e-m-b-a__embark METASEP

embark/static/content/images/header.png METASEP

embark/static/content/images/favicon.png METASEP

embark/static/content/images/embark_logo_text.svg METASEP
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   width="127.82402mm"
   height="19.583677mm"
   viewBox="0 0 127.82402 19.583677"
   version="1.1"
   id="svg10323"
   inkscape:version="1.1 (c68e22c387, 2021-05-23)"
   sodipodi:docname="emba_logo_text.svg"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <sodipodi:namedview
     id="namedview10325"
     pagecolor="#505050"
     bordercolor="#eeeeee"
     borderopacity="1"
     inkscape:pageshadow="0"
     inkscape:pageopacity="0"
     inkscape:pagecheckerboard="0"
     inkscape:document-units="mm"
     showgrid="false"
     inkscape:zoom="0.77771465"
     inkscape:cx="299.59575"
     inkscape:cy="391.53178"
     inkscape:window-width="3440"
     inkscape:window-height="1387"
     inkscape:window-x="-8"
     inkscape:window-y="-8"
     inkscape:window-maximized="1"
     inkscape:current-layer="layer1" />
  <defs
     id="defs10320" />
  <g
     inkscape:label="Ebene 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(-25.902422,-44.981359)">
    <g
       id="g13699-8"
       transform="translate(-4.9189163,-38.219634)"
       style="fill:#ffffff;fill-opacity:1">
      <g
         aria-label="EMBArk"
         id="text5988-2"
         style="font-size:22.5778px;line-height:1.25;fill:#ffffff;stroke-width:0.264583">
        <path
           d="M 74.827486,101.20058 H 62.954297 V 84.785369 h 11.873189 v 3.175003 h -7.6619 v 2.83325 h 7.110684 v 3.175003 h -7.110684 v 4.056949 h 7.6619 z"
           style="font-weight:bold;-inkscape-font-specification:'sans-serif Bold';fill:#ffffff"
           id="path22702" />
        <path
           d="M 95.586274,101.20058 H 91.374985 V 90.209333 l -3.042711,7.132733 H 85.41083 L 82.368119,90.209333 V 101.20058 H 78.377316 V 84.785369 h 4.916845 l 3.693146,8.235165 3.682122,-8.235165 h 4.916845 z"
           style="font-weight:bold;-inkscape-font-specification:'sans-serif Bold';fill:#ffffff"
           id="path22704" />
        <path
           d="m 113.8205,96.173488 q 0,1.190627 -0.48507,2.127693 -0.47405,0.937067 -1.3119,1.554429 -0.97014,0.72761 -2.13871,1.03629 -1.15756,0.30868 -2.9435,0.30868 H 99.775517 V 84.785369 h 6.372053 q 1.98438,0 2.8994,0.132292 0.92604,0.132292 1.83003,0.584289 0.93707,0.474045 1.38907,1.27882 0.46302,0.793751 0.46302,1.819013 0,1.190626 -0.62839,2.105644 -0.62838,0.903994 -1.77491,1.411113 v 0.08819 q 1.60955,0.319705 2.54662,1.322918 0.94809,1.003213 0.94809,2.645836 z m -5.42397,-6.746882 q 0,-0.407899 -0.20946,-0.815799 -0.19844,-0.4079 -0.71658,-0.606337 -0.46302,-0.176389 -1.15755,-0.187414 -0.68351,-0.02205 -1.92926,-0.02205 h -0.39687 v 3.472659 h 0.66145 q 1.00322,0 1.70877,-0.03307 0.70556,-0.03307 1.11346,-0.220486 0.57326,-0.253559 0.74965,-0.650435 0.17639,-0.407899 0.17639,-0.937067 z m 1.03629,6.680736 q 0,-0.782726 -0.30868,-1.20165 -0.29766,-0.429948 -1.02526,-0.63941 -0.4961,-0.143317 -1.36702,-0.154341 -0.87092,-0.01102 -1.81901,-0.01102 h -0.92604 v 4.090021 h 0.30868 q 1.78594,0 2.55764,-0.01102 0.7717,-0.01102 1.42214,-0.286632 0.66145,-0.275608 0.90399,-0.727605 0.25356,-0.463021 0.25356,-1.058335 z"
           style="font-weight:bold;-inkscape-font-specification:'sans-serif Bold';fill:#ffffff"
           id="path22706" />
        <path
           d="m 132.14291,101.20058 h -4.37665 l -1.13551,-3.318323 h -6.08542 l -1.13551,3.318323 h -4.26641 l 6.06338,-16.415211 h 4.87275 z m -6.53742,-6.327961 -2.01745,-5.886985 -2.01745,5.886985 z"
           style="font-weight:bold;-inkscape-font-specification:'sans-serif Bold';fill:#ffffff"
           id="path22708" />
        <path
           d="m 143.25543,92.60161 h -0.35278 q -0.25356,-0.08819 -0.8158,-0.132292 -0.56224,-0.0441 -0.93707,-0.0441 -0.84887,0 -1.49931,0.110243 -0.65043,0.110243 -1.40008,0.374827 v 8.290292 h -3.96876 V 88.820269 h 3.96876 v 1.819012 q 1.31189,-1.12448 2.28203,-1.488282 0.97014,-0.374827 1.78594,-0.374827 0.20946,0 0.47404,0.01102 0.26459,0.01102 0.46303,0.03307 z"
           style="font-weight:bold;-inkscape-font-specification:'sans-serif Bold';fill:#ffffff"
           id="path22710" />
        <path
           d="m 158.64536,101.20058 h -4.61918 l -3.47266,-5.390894 -1.08039,1.311894 v 4.079 h -3.96875 V 84.04674 h 3.96875 v 10.24159 l 4.27744,-5.468061 h 4.57509 l -4.45382,5.335769 z"
           style="font-weight:bold;-inkscape-font-specification:'sans-serif Bold';fill:#ffffff"
           id="path22712" />
      </g>
    </g>
    <path
       id="path11628-7-8-2"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.180459"
       d="m 29.595574,49.288374 a 2.2975087,2.2975087 0 0 0 -2.2973,2.29729 2.2975087,2.2975087 0 0 0 2.2973,2.29765 2.2975087,2.2975087 0 0 0 2.29764,-2.29765 2.2975087,2.2975087 0 0 0 -2.29764,-2.29729 z m 0,0.88224 a 1.4150356,1.4150356 0 0 1 1.41504,1.41505 1.4150356,1.4150356 0 0 1 -1.41504,1.41505 1.4150356,1.4150356 0 0 1 -1.41469,-1.41505 1.4150356,1.4150356 0 0 1 1.41469,-1.41505 z" />
    <path
       id="path11067-2-5"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.175088"
       d="m 39.303184,47.599654 a 1.410378,1.410378 0 0 0 -1.41024,1.41024 1.410378,1.410378 0 0 0 1.41024,1.41056 1.410378,1.410378 0 0 0 1.41058,-1.41056 1.410378,1.410378 0 0 0 -1.41058,-1.41024 z m 0.0152,0.79844 a 0.61207631,0.61207631 0 0 1 0.61214,0.6118 0.61207631,0.61207631 0 0 1 -0.61214,0.61213 0.61207631,0.61207631 0 0 1 -0.61214,-0.61213 0.61207631,0.61207631 0 0 1 0.61214,-0.6118 z" />
    <path
       id="path11067-2-5-4"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.175088"
       d="m 42.223454,59.126214 a 1.410378,1.410378 0 0 0 -1.41024,1.41024 1.410378,1.410378 0 0 0 1.41024,1.41056 1.410378,1.410378 0 0 0 1.41059,-1.41056 1.410378,1.410378 0 0 0 -1.41059,-1.41024 z m 0.0152,0.79844 a 0.61207631,0.61207631 0 0 1 0.61215,0.6118 0.61207631,0.61207631 0 0 1 -0.61215,0.61213 0.61207631,0.61207631 0 0 1 -0.61214,-0.61213 0.61207631,0.61207631 0 0 1 0.61214,-0.6118 z" />
    <path
       d="m 54.860384,54.161144 h -2.55416 -2.55437 v 1.22439 h 2.55437 2.55416 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785113"
       id="rect1210-09-7-1-5-4-9-22" />
    <path
       d="m 54.860384,50.973464 h -2.55416 -2.55437 v 1.2244 h 2.55437 2.55416 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785113"
       id="rect1210-09-7-1-5-4-9-22-9" />
    <path
       d="m 36.155024,50.973464 h -2.55416 -2.55437 v 1.2244 h 2.55437 2.55416 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785113"
       id="rect1210-09-7-1-5-4-9-22-9-2" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-01-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785109"
       d="m 49.751854,50.973464 -1.80622,1.80623 -1.80621,1.80621 0.18772,0.18772 h 1.35588 l 1.12827,-1.12778 1.80622,-1.80623 z" />
    <path
       d="m 47.683024,56.736904 h -10.25641 -10.25729 v 1.22439 h 10.25729 10.25641 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.157326"
       id="rect1210-09-7-1-5-4-9-3-0-9" />
    <path
       d="m 54.860384,57.348814 h -2.55415 -2.55437 v 1.2244 h 2.55437 2.55415 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785108"
       id="rect1210-09-7-1-5-4-9-6-3" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-9"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785109"
       d="m 49.752044,57.348614 -1.80622,1.80622 -1.80622,1.80621 0.18772,0.18772 h 1.35588 l 1.12828,-1.1278 1.80622,-1.8062 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-01"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785109"
       d="m 49.751854,54.161144 -1.80622,1.80622 -1.80621,1.80621 0.18772,0.18772 h 1.35588 l 1.12827,-1.12778 1.80622,-1.80622 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785109"
       d="m 49.751854,52.197864 -1.80622,-1.80622 -1.80621,-1.80621 0.18772,-0.18773 h 1.35588 l 1.12827,1.1278 1.80622,1.80621 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-8-2"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785109"
       d="m 38.223854,54.773624 -1.80622,-1.80622 -1.80621,-1.80621 0.18772,-0.18773 h 1.35588 l 1.12827,1.1278 1.80622,1.80621 z" />
    <path
       d="m 54.860384,47.785774 h -2.55416 -2.55437 v 1.22439 h 2.55437 2.55416 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785113"
       id="rect1210-09-7-1-5-4-9-22-9-1" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-8-6"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785109"
       d="m 49.751854,49.010164 -1.80622,-1.80622 -1.80621,-1.80621 0.18772,-0.18772 h 1.35588 l 1.12827,1.1278 1.80622,1.8062 z" />
    <path
       d="m 47.683534,59.924304 h -2.41627 -2.41646 v 1.2244 h 2.41646 2.41627 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.076362"
       id="rect1210-09-7-1-5-4-9-3-0-2-3" />
    <path
       d="m 54.860264,60.536504 h -2.55415 -2.55437 v 1.2244 h 2.55437 2.55415 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785108"
       id="rect1210-09-7-1-5-4-9-6-4-2" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8-9"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0785109"
       d="m 49.751924,60.536294 -1.80622,1.80623 -1.80622,1.8062 0.18773,0.18773 h 1.35587 l 1.12828,-1.1278 1.80622,-1.80621 z" />
    <path
       d="m 47.683414,63.111994 h -5.6237 -5.6242 v 1.22439 h 5.6242 5.6237 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.116498"
       id="rect1210-09-7-1-5-4-9-3-0-2-2-0" />
    <path
       id="rect1210-09-7-1-0-2-2-0-19-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.106833"
       d="m 47.683024,53.549244 h -4.72938 -4.72979 v 1.22438 h 4.72979 4.72938 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-19-8-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0967166"
       d="m 47.683024,48.397704 h -3.87608 -3.87642 v 1.22438 h 3.87642 3.87608 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-19-8-0-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0731372"
       d="m 47.683024,45.210014 h -2.2165 -2.2167 v 1.22438 h 2.2167 2.2165 z" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261579"
       id="path12153-6-8"
       cx="26.784866"
       cy="57.34911"
       rx="0.88244396"
       ry="0.84084958" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261579"
       id="path12153-6-8-7"
       cx="36.399796"
       cy="63.724186"
       rx="0.88244396"
       ry="0.84084958" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261579"
       id="path12153-6-8-5"
       cx="43.214096"
       cy="45.822208"
       rx="0.88244396"
       ry="0.84084958" />
  </g>
</svg>

embark/static/content/images/embark_logo_text.png METASEP

embark/static/content/images/embark_logo.svg METASEP
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   width="153.11989mm"
   height="81.739853mm"
   viewBox="0 0 153.11989 81.739853"
   version="1.1"
   id="svg2336"
   inkscape:export-filename="C:\Users\z003xz3p\Desktop\emba_logo.png"
   inkscape:export-xdpi="96"
   inkscape:export-ydpi="96"
   inkscape:version="1.1 (c68e22c387, 2021-05-23)"
   sodipodi:docname="embark_logo.svg"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <sodipodi:namedview
     id="namedview2338"
     pagecolor="#505050"
     bordercolor="#eeeeee"
     borderopacity="1"
     inkscape:pageshadow="0"
     inkscape:pageopacity="0"
     inkscape:pagecheckerboard="0"
     inkscape:document-units="mm"
     showgrid="false"
     inkscape:zoom="0.77771465"
     inkscape:cx="294.45247"
     inkscape:cy="462.25181"
     inkscape:window-width="3440"
     inkscape:window-height="1387"
     inkscape:window-x="-8"
     inkscape:window-y="-8"
     inkscape:window-maximized="1"
     inkscape:current-layer="layer1" />
  <defs
     id="defs2333" />
  <g
     inkscape:label="Ebene 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(-27.202939,-26.150693)">
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-0-7"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 90.175837,35.694206 1.079521,1.079521 1.91926,1.919264 0.91984,-0.920358 -1.91926,-1.919264 -0.15916,-0.159163 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-0-3"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 92.254258,37.772633 1.07952,1.079521 1.91926,1.919264 0.91984,-0.920358 -1.91926,-1.919264 -0.15916,-0.159163 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-0-3-9"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 88.097418,33.615779 1.07952,1.079521 1.91926,1.919264 0.91984,-0.920358 -1.91926,-1.919264 -0.15916,-0.159163 z" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-0"
       cx="67.578865"
       cy="85.47802"
       rx="0.93767363"
       ry="0.89347595" />
    <path
       id="rect1210-09-7-1-7-7-5-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.101546"
       d="m 86.694187,49.314147 h -4.021122 -4.021473 v 1.301021 h 4.021473 4.021122 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.149545"
       d="m 79.889735,43.05564 h -8.720917 -8.721672 v 1.301021 h 8.721672 8.720917 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0971489"
       d="m 92.016038,35.694206 h -3.680392 -3.680711 v 1.301021 h 3.680711 3.680392 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-0-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.102113"
       d="M 89.937618,33.615779 H 85.871512 81.805055 V 34.9168 h 4.066457 4.066106 z" />
    <rect
       y="45.832386"
       x="87.995155"
       height="30.157974"
       width="30.157982"
       id="rect10"
       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke-width:0.064204" />
    <path
       id="rect1726"
       style="fill:#29043f;fill-opacity:1;stroke:none;stroke-width:0.0751967"
       d="m 89.296127,61.561646 v 6.432682 0.262517 6.432682 h 4.200777 8.926586 v -5.913334 h -1.31516 V 67.47498 h 1.31516 v -5.913334 h -8.926586 z m 4.200777,3.652491 h 4.72581 v 1.301213 h -4.72581 z m 0,4.460708 h 4.72581 v 1.300697 h -4.72581 z" />
    <path
       d="m 103.72466,47.133305 v 4.200878 8.926653 h 3.68049 v -8.926541 h 0.52027 v -1.12e-4 h 0.26262 v 1.12e-4 h 0.51813 v 8.926541 h 3.16337 v -8.926653 h 0.51926 0.26251 0.51925 v 8.926653 h 3.68162 v -8.926653 -4.200878 h -4.20087 -0.26251 -4.20076 -0.26262 z"
       style="fill:#29043f;fill-opacity:1;stroke-width:0.0542886"
       id="rect127-6" />
    <path
       d="m 89.296108,47.133305 v 4.200878 0.262505 4.200765 0.262619 4.200764 h 4.200878 8.926644 v -3.68444 h -8.926644 v -0.516324 -0.262619 -0.521965 h 8.926644 v -3.159542 h -8.926644 v -0.519258 -0.262505 -0.519145 h 8.926644 v -3.681733 h -8.926644 z"
       style="fill:#29043f;fill-opacity:1;stroke-width:0.0542886"
       id="rect127" />
    <path
       d="m 103.72466,61.561855 v 13.127533 h 5.91319 v -4.200879 h 0.51926 0.26251 0.51925 v 4.200879 h 5.91331 V 61.561855 Z m 5.91319,3.681622 h 1.30102 v 1.300908 h -1.30102 z"
       style="fill:#29043f;fill-opacity:1;stroke-width:0.147399"
       id="rect85-8-1" />
    <path
       id="rect1210-09-7-1-5-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0577579"
       d="m 122.05604,60.260864 h -1.30091 -1.30102 v 1.30102 h 1.30102 1.30091 z" />
    <path
       d="m 130.19823,49.314147 h -5.37183 -5.37229 v 1.30102 h 5.37229 5.37183 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.117368"
       id="rect1210-09-7-1-7-7-5" />
    <path
       d="m 124.82596,54.787505 h -2.68581 -2.68604 v 1.301021 h 2.68604 2.68581 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0829899"
       id="rect1210-09-7-1-0-2-2" />
    <path
       id="rect1210-09-7-1-0-8-5-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.203505"
       d="M 151.75552,65.734222 H 135.6055 119.45411 v 1.301021 h 16.15139 16.15002 z" />
    <path
       id="rect1210-09-7-1-7-9-9-5"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.107232"
       d="m 128.42271,71.207583 h -4.48411 -4.48449 v 1.30102 h 4.48449 4.48411 z" />
    <path
       id="rect1210-09-7-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.143479"
       d="m 102.42363,28.474952 v 8.027887 8.028578 h 1.30103 v -8.028578 -8.027887 z" />
    <path
       id="rect1210-09-7-1-2"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0859559"
       d="m 99.686675,32.622362 v 2.881228 2.881476 H 100.9877 V 35.50359 32.622362 Z" />
    <path
       d="m 91.476922,41.929487 v 1.300909 1.301021 h 1.301021 v -1.301021 -1.300909 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0577579"
       id="rect1210-09-7-1-7" />
    <path
       d="m 80.667489,41.751013 v 2.992501 2.992759 h 1.301021 v -2.992759 -2.992501 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0875998"
       id="rect1210-09-7-1-7-3" />
    <path
       d="m 96.950285,41.929487 v 1.300909 1.301021 h 1.30102 v -1.301021 -1.300909 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0577579"
       id="rect1210-09-7-1-0" />
    <path
       id="rect1210-09-7-1-0-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.102824"
       d="m 107.89699,36.285001 v 4.12303 4.123386 h 1.30102 v -4.123386 -4.12303 z" />
    <path
       id="rect1210-09-7-1-7-9"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0577579"
       d="m 113.37035,41.929487 v 1.300909 1.301021 h 1.30102 v -1.301021 -1.300909 z" />
    <path
       d="m 102.42363,77.291333 v 1.300907 1.30102 h 1.30103 v -1.30102 -1.300907 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0577579"
       id="rect1210-09-7-1-5" />
    <path
       id="rect1210-09-7-1-7-7"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0577579"
       d="m 91.476923,77.291333 v 1.300907 1.30102 h 1.30102 v -1.30102 -1.300907 z" />
    <path
       id="rect1210-09-7-1-0-2"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0577579"
       d="m 96.950285,77.291333 v 1.300907 1.30102 h 1.30102 v -1.30102 -1.300907 z" />
    <path
       d="m 107.89699,77.291333 v 3.008813 3.009074 h 1.30102 v -3.009074 -3.008813 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0878387"
       id="rect1210-09-7-1-0-8-5" />
    <path
       d="m 113.37035,77.291333 v 1.300907 1.30102 h 1.30102 v -1.30102 -1.300907 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0577579"
       id="rect1210-09-7-1-7-9-9" />
    <path
       d="M 86.694187,60.260864 H 70.734958 54.77435 v 1.30102 h 15.960608 15.959229 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.2023"
       id="rect1210-09-7-1-5-4-9" />
    <path
       d="M 78.772761,78.59224 H 62.813532 46.852924 v 1.30102 h 15.960608 15.959229 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.2023"
       id="rect1210-09-7-1-5-4-9-2" />
    <path
       d="M 79.067617,62.99784 H 68.16929 57.27002 v 1.301021 h 10.89927 10.898327 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.167173"
       id="rect1210-09-7-1-5-4-9-3-0" />
    <path
       d="M 52.576033,62.99784 H 41.677706 30.778436 v 1.301021 h 10.89927 10.898327 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.167173"
       id="rect1210-09-7-1-5-4-9-3-0-6" />
    <path
       id="rect1210-09-7-1-5-4-9-3-0-5"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.171469"
       d="m 69.805842,62.997735 c 0,0 -1.463045,1.463046 -1.919261,1.919261 -0.41529,0.41529 -0.818038,0.818039 -0.818038,0.818039 H 56.142079 44.675596 v 1.300694 h 11.466483 10.024711 1.440738 l 1.198893,-1.198374 1.919264,-1.919264 z"
       sodipodi:nodetypes="cscccccccccc" />
    <path
       d="m 86.694187,65.734223 h -2.714009 -2.714243 v 1.30102 h 2.714243 2.714009 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834246"
       id="rect1210-09-7-1-5-4-9-6" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 81.266126,65.734001 -1.919263,1.919263 -1.919261,1.919261 0.199469,0.199472 h 1.440738 l 1.198894,-1.198377 1.919263,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 91.858103,78.972901 -1.919264,1.919264 -1.919261,1.919261 0.199469,0.199472 h 1.440739 l 1.198893,-1.198377 1.919264,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-81"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 108.81683,37.205361 1.91926,-1.91927 1.91926,-1.919261 -0.19946,-0.19947 h -1.44074 l -1.1989,1.19838 -1.91926,1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-81-7"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 114.29019,42.849847 1.91926,-1.91927 1.91926,-1.919261 -0.19946,-0.19947 h -1.44074 l -1.1989,1.198381 -1.91926,1.91926 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-81-7-2"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 114.01599,36.152902 -0.66818,0.66766 -1.91926,1.919264 0.91984,0.920356 1.91926,-1.919262 0.81546,-0.815453 h -1.06712 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-81-7-3"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 121.90396,40.112867 1.91926,-1.91927 1.91926,-1.919261 -0.19946,-0.19947 h -1.44074 l -1.1989,1.198381 -1.91926,1.91926 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-81-7-3-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 132.30564,37.375887 1.91926,-1.91927 1.91926,-1.919261 -0.19946,-0.19947 h -1.44074 l -1.1989,1.198381 -1.91926,1.91926 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 97.331295,78.972986 -1.919263,1.919264 -1.028877,1.028877 v 1.089856 h 0.749823 l 1.198894,-1.198378 1.919263,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 102.80482,78.972901 -1.91926,1.919264 -1.919265,1.919261 0.19947,0.199472 h 1.440735 l 1.19889,-1.198377 1.91927,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-0-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 99.686665,82.090539 -1.91926,1.919264 -1.91927,1.919261 0.19947,0.199472 h 1.44074 l 1.19889,-1.198377 1.919265,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 81.265934,60.260864 -1.919264,1.919264 -1.919261,1.919261 0.19947,0.199472 h 1.440738 l 1.198893,-1.198377 1.919264,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-00"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 103.72466,40.583383 -1.91926,-1.919264 -1.919255,-1.919261 -0.19947,0.19947 v 1.440738 l 1.198365,1.198893 1.91926,1.919264 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-00-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 97.491493,34.34987 v 0.961699 h -0.541571 v 0.336412 l 1.198378,1.198894 1.91926,1.919263 0.92036,-0.919839 -1.919265,-1.919264 z" />
    <path
       d="M 70.737528,57.524482 H 59.839201 48.939931 v 1.30102 h 10.89927 10.898327 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.167173"
       id="rect1210-09-7-1-5-4-9-3-0-35" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-9"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 72.935845,54.787506 -1.919264,1.919263 -1.919261,1.919261 0.19947,0.199472 h 1.440738 l 1.198893,-1.198377 1.919264,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-45"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 70.364984,74.329342 -0.165884,0.165881 -1.919261,1.919263 0.19947,0.199472 h 1.440741 l 1.198893,-1.198377 0.721918,-0.721921 h -1.475877 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 80.971078,82.630237 -1.919264,-1.919264 -1.919262,-1.919261 0.199471,-0.199472 h 1.440738 l 1.198892,1.198377 1.919264,1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 83.793338,61.561884 -1.919264,-1.919264 -1.919262,-1.919261 0.199471,-0.199472 h 1.440738 l 1.198892,1.198377 1.919264,1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 80.667713,46.577032 v 1.1591 l 0.95963,0.959634 1.919264,1.919261 0.91984,-0.920356 -1.919261,-1.919264 -1.198896,-1.198375 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 78.049785,43.055799 1.079521,1.079521 1.919261,1.919264 0.919843,-0.920359 -1.919264,-1.919263 -0.159163,-0.159163 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.138662"
       d="m 79.350647,39.66845 h -7.497851 -7.498501 v 1.301021 h 7.498501 7.497851 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-1-2"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0686944"
       d="M 90.699516,39.85106 H 88.859318 87.01896 v 1.301021 h 1.840358 1.840198 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-2"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834822"
       d="m 78.049885,39.66845 1.079521,1.081011 1.919261,1.92191 0.919843,-0.921627 -1.919264,-1.921911 -0.159163,-0.159383 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 88.859318,39.85106 1.079521,1.079521 1.919261,1.919264 0.919843,-0.920358 -1.919264,-1.919264 -0.159163,-0.159163 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-0-58"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 94.33268,39.85106 1.079521,1.079521 1.919264,1.919264 0.91984,-0.920358 -1.919264,-1.919264 -0.159163,-0.159163 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-9"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 45.393381,76.593802 v 1.280544 h -0.559657 l 0.09974,0.09973 1.919261,1.919263 0.91984,-0.920358 -1.919261,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-6"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 68.479309,72.575962 -0.19947,0.199469 1.919261,1.919264 0.45992,0.459919 V 74.69366 h 1.379244 L 71.118943,73.774336 69.92005,72.575962 Z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 54.77435,60.260864 -1.919264,1.919264 -1.919261,1.919261 0.19947,0.199472 h 1.440738 l 1.198893,-1.198377 1.919264,-1.919261 z" />
    <path
       d="m 79.068155,68.470901 h -2.567487 -2.56771 v 1.301022 h 2.56771 2.567487 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0811413"
       id="rect1210-09-7-1-5-4-9-3-0-2" />
    <path
       d="m 86.694187,71.207583 h -2.714009 -2.714244 v 1.30102 h 2.714244 2.714009 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834246"
       id="rect1210-09-7-1-5-4-9-6-4" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 81.266126,71.207361 -1.919264,1.919264 -1.919261,1.919261 0.199469,0.199472 h 1.440739 l 1.198893,-1.198377 1.919264,-1.919261 z" />
    <path
       d="m 79.068155,73.94426 h -4.204442 -4.204808 v 1.301023 h 4.204808 4.204442 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.103835"
       id="rect1210-09-7-1-5-4-9-3-0-2-2" />
    <path
       d="M 71.46076,68.470595 H 56.506664 41.551284 v 1.30102 h 14.95538 14.954096 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.195825"
       id="rect1210-09-7-1-5-4-9-3-0-2-0" />
    <path
       d="M 97.488345,84.827516 H 82.534248 67.578869 v 1.30102 h 14.955379 14.954097 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.195825"
       id="rect1210-09-7-1-5-4-9-3-0-2-0-2" />
    <path
       d="m 140.92888,84.827514 h -13.39721 -13.39836 v 1.30102 h 13.39836 13.39721 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.185351"
       id="rect1210-09-7-1-5-4-9-3-0-2-0-2-6" />
    <path
       d="m 134.61877,81.709877 h -9.06474 -9.06552 v 1.30102 h 9.06552 9.06474 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.152463"
       id="rect1210-09-7-1-5-4-9-3-0-2-0-2-6-1" />
    <path
       id="rect1210-09-7-1-0-2-2-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.207984"
       d="M 86.694187,54.787506 H 69.825323 52.954993 v 1.30102 h 16.87033 16.868864 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-19"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0607826"
       d="m 81.595021,57.523887 h -1.440738 -1.440864 v 1.30102 h 1.440864 1.440738 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-7"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.199609"
       d="M 89.659786,81.709878 H 74.122049 58.582962 v 1.30102 h 15.539087 15.537737 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-9"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.151323"
       d="m 73.310832,49.314148 h -8.929568 -8.930342 v 1.30102 h 8.930342 8.929568 z" />
    <path
       id="path11628"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.266049"
       d="m 75.925659,46.577785 a 3.38719,3.38719 0 0 0 -3.386873,3.386873 3.38719,3.38719 0 0 0 3.386873,3.387388 3.38719,3.38719 0 0 0 3.387389,-3.387388 3.38719,3.38719 0 0 0 -3.387389,-3.386873 z m 0,1.300694 a 2.0861702,2.0861702 0 0 1 2.086176,2.086179 2.0861702,2.0861702 0 0 1 -2.086176,2.086176 2.0861702,2.0861702 0 0 1 -2.08566,-2.086176 2.0861702,2.0861702 0 0 1 2.08566,-2.086179 z" />
    <path
       id="path11628-7"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.266049"
       d="m 59.663039,40.319277 a 3.38719,3.38719 0 0 0 -3.386873,3.386873 3.38719,3.38719 0 0 0 3.386873,3.387389 3.38719,3.38719 0 0 0 3.387389,-3.387389 3.38719,3.38719 0 0 0 -3.387389,-3.386873 z m 0,1.300694 a 2.0861702,2.0861702 0 0 1 2.086176,2.086179 2.0861702,2.0861702 0 0 1 -2.086176,2.086177 2.0861702,2.0861702 0 0 1 -2.08566,-2.086177 2.0861702,2.0861702 0 0 1 2.08566,-2.086179 z" />
    <path
       id="path11628-7-97"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.266049"
       d="m 79.130148,30.879416 a 3.38719,3.38719 0 0 0 -3.386873,3.386873 3.38719,3.38719 0 0 0 3.386873,3.387389 3.38719,3.38719 0 0 0 3.387389,-3.387389 3.38719,3.38719 0 0 0 -3.387389,-3.386873 z m 0,1.300694 a 2.0861702,2.0861702 0 0 1 2.086175,2.086179 2.0861702,2.0861702 0 0 1 -2.086175,2.086177 2.0861702,2.0861702 0 0 1 -2.085661,-2.086177 2.0861702,2.0861702 0 0 1 2.085661,-2.086179 z" />
    <path
       id="path11628-7-8"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.266049"
       d="m 38.693062,65.734231 a 3.38719,3.38719 0 0 0 -3.386873,3.386873 3.38719,3.38719 0 0 0 3.386873,3.387389 3.38719,3.38719 0 0 0 3.387389,-3.387388 3.38719,3.38719 0 0 0 -3.387389,-3.386874 z m 0,1.300694 a 2.0861702,2.0861702 0 0 1 2.086176,2.086179 2.0861702,2.0861702 0 0 1 -2.086176,2.086177 2.0861702,2.0861702 0 0 1 -2.08566,-2.086177 2.0861702,2.0861702 0 0 1 2.08566,-2.086179 z" />
    <path
       id="path11628-7-8-4"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.266049"
       d="m 36.090836,49.314759 a 3.38719,3.38719 0 0 0 -3.386873,3.386873 3.38719,3.38719 0 0 0 3.386873,3.38739 3.38719,3.38719 0 0 0 3.387389,-3.387388 3.38719,3.38719 0 0 0 -3.387389,-3.386875 z m 0,1.300694 a 2.0861702,2.0861702 0 0 1 2.086176,2.086179 2.0861702,2.0861702 0 0 1 -2.086176,2.086177 2.0861702,2.0861702 0 0 1 -2.08566,-2.086177 2.0861702,2.0861702 0 0 1 2.08566,-2.086179 z" />
    <g
       id="g13699"
       transform="translate(-5.4148351,6.6899678)"
       style="fill:#ffffff;fill-opacity:1">
      <text
         xml:space="preserve"
         style="font-style:normal;font-weight:normal;font-size:22.5778px;line-height:1.25;font-family:sans-serif;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583"
         x="60.859676"
         y="101.20058"
         id="text5988"><tspan
           sodipodi:role="line"
           id="tspan5986"
           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:22.5778px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#ffffff;fill-opacity:1;stroke-width:0.264583"
           x="60.859676"
           y="101.20058">EMBArk</tspan></text>
    </g>
    <path
       id="path11067"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 72.81599,67.012963 a 2.1086066,2.1086066 0 0 0 -2.108399,2.108398 2.1086066,2.1086066 0 0 0 2.108399,2.108914 2.1086066,2.1086066 0 0 0 2.108917,-2.108914 2.1086066,2.1086066 0 0 0 -2.108917,-2.108398 z m 0.02274,1.193726 a 0.91509383,0.91509383 0 0 1 0.915191,0.914672 0.91509383,0.91509383 0 0 1 -0.915191,0.915188 0.91509383,0.91509383 0 0 1 -0.915188,-0.915188 0.91509383,0.91509383 0 0 1 0.915188,-0.914672 z" />
    <path
       id="path11067-2"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 44.316122,74.346053 a 2.1086066,2.1086066 0 0 0 -2.108399,2.108398 2.1086066,2.1086066 0 0 0 2.108399,2.108914 2.1086066,2.1086066 0 0 0 2.108917,-2.108914 2.1086066,2.1086066 0 0 0 -2.108917,-2.108398 z m 0.02274,1.193725 a 0.91509383,0.91509383 0 0 1 0.915191,0.914673 0.91509383,0.91509383 0 0 1 -0.915191,0.915187 0.91509383,0.91509383 0 0 1 -0.915188,-0.915188 0.91509383,0.91509383 0 0 1 0.915188,-0.914672 z" />
    <path
       id="path11067-8"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 83.769569,74.939989 a 2.1086066,2.1086066 0 0 0 -2.10839,2.108398 2.1086066,2.1086066 0 0 0 2.10839,2.108914 2.1086066,2.1086066 0 0 0 2.10892,-2.108914 2.1086066,2.1086066 0 0 0 -2.10892,-2.108398 z m 0.0227,1.193726 a 0.91509383,0.91509383 0 0 1 0.9152,0.914672 0.91509383,0.91509383 0 0 1 -0.9152,0.915188 0.91509383,0.91509383 0 0 1 -0.91518,-0.915188 0.91509383,0.91509383 0 0 1 0.91518,-0.914672 z" />
    <path
       id="path11067-7"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 29.311338,61.539952 a 2.1086066,2.1086066 0 0 0 -2.108399,2.108398 2.1086066,2.1086066 0 0 0 2.108399,2.108914 2.1086066,2.1086066 0 0 0 2.108918,-2.108914 2.1086066,2.1086066 0 0 0 -2.108918,-2.108398 z m 0.02274,1.193726 a 0.91509383,0.91509383 0 0 1 0.915191,0.914672 0.91509383,0.91509383 0 0 1 -0.915191,0.915188 0.91509383,0.91509383 0 0 1 -0.915188,-0.915188 0.91509383,0.91509383 0 0 1 0.915188,-0.914672 z" />
    <path
       id="path11067-7-9"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 47.889346,56.066287 a 2.1086066,2.1086066 0 0 0 -2.108399,2.108398 2.1086066,2.1086066 0 0 0 2.108399,2.108914 2.1086066,2.1086066 0 0 0 2.108919,-2.108914 2.1086066,2.1086066 0 0 0 -2.108919,-2.108398 z m 0.02274,1.193726 a 0.91509383,0.91509383 0 0 1 0.915191,0.914672 0.91509383,0.91509383 0 0 1 -0.915191,0.915188 0.91509383,0.91509383 0 0 1 -0.915188,-0.915188 0.91509383,0.91509383 0 0 1 0.915188,-0.914672 z" />
    <path
       id="path11067-7-9-3"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 85.968375,38.393172 a 2.1086066,2.1086066 0 0 0 -2.108399,2.108399 2.1086066,2.1086066 0 0 0 2.108399,2.108913 2.1086066,2.1086066 0 0 0 2.108919,-2.108913 2.1086066,2.1086066 0 0 0 -2.108919,-2.108399 z m 0.02274,1.193726 a 0.91509383,0.91509383 0 0 1 0.915191,0.914673 0.91509383,0.91509383 0 0 1 -0.915191,0.915188 0.91509383,0.91509383 0 0 1 -0.915188,-0.915188 0.91509383,0.91509383 0 0 1 0.915188,-0.914673 z" />
    <path
       id="path11067-7-9-3-8"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 127.11622,56.06637 a 2.1086066,2.1086066 0 0 0 -2.1084,2.108399 2.1086066,2.1086066 0 0 0 2.1084,2.108913 2.1086066,2.1086066 0 0 0 2.10892,-2.108913 2.1086066,2.1086066 0 0 0 -2.10892,-2.108399 z m 0.0227,1.193725 a 0.91509383,0.91509383 0 0 1 0.91519,0.914674 0.91509383,0.91509383 0 0 1 -0.91519,0.915188 0.91509383,0.91509383 0 0 1 -0.91519,-0.915188 0.91509383,0.91509383 0 0 1 0.91519,-0.914674 z" />
    <path
       id="path11067-7-9-3-8-8"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 108.55797,82.719117 a 2.1086066,2.1086066 0 0 0 -2.1084,2.108399 2.1086066,2.1086066 0 0 0 2.1084,2.108913 2.1086066,2.1086066 0 0 0 2.10892,-2.108913 2.1086066,2.1086066 0 0 0 -2.10892,-2.108399 z m 0.0227,1.193725 a 0.91509383,0.91509383 0 0 1 0.91519,0.914674 0.91509383,0.91509383 0 0 1 -0.91519,0.915188 0.91509383,0.91509383 0 0 1 -0.91519,-0.915188 0.91509383,0.91509383 0 0 1 0.91519,-0.914674 z" />
    <path
       id="path11067-7-9-3-0"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 96.211254,31.899401 a 2.1086066,2.1086066 0 0 0 -2.108399,2.108399 2.1086066,2.1086066 0 0 0 2.108399,2.108913 2.1086066,2.1086066 0 0 0 2.108921,-2.108913 2.1086066,2.1086066 0 0 0 -2.108921,-2.108399 z m 0.02274,1.193726 a 0.91509383,0.91509383 0 0 1 0.915191,0.914673 0.91509383,0.91509383 0 0 1 -0.915191,0.915188 0.91509383,0.91509383 0 0 1 -0.915188,-0.915188 0.91509383,0.91509383 0 0 1 0.915188,-0.914673 z" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153"
       cx="44.877903"
       cy="66.396271"
       rx="0.93767363"
       ry="0.89347595" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-8"
       cx="94.794914"
       cy="82.411705"
       rx="0.93767363"
       ry="0.89347595" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6"
       cx="56.861511"
       cy="63.64835"
       rx="0.93767363"
       ry="0.89347595" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-2"
       cx="52.546486"
       cy="55.438015"
       rx="0.93767363"
       ry="0.89347595" />
    <path
       d="M 65.423872,52.051124 H 52.187038 38.949058 v 1.301021 h 13.23798 13.236834 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.184238"
       id="rect1210-09-7-1-5-4-9-3-0-65" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 67.622189,49.314148 -1.919264,1.919264 -1.919261,1.919261 0.19947,0.199472 h 1.440738 l 1.198893,-1.198377 1.919264,-1.919261 z" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-7"
       cx="55.45092"
       cy="49.964657"
       rx="0.93767363"
       ry="0.89347595" />
    <path
       d="m 69.919931,75.312749 h -7.975193 -7.975883 v 1.301021 h 7.975883 7.975193 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.143007"
       id="rect1210-09-7-1-5-4-9-3-0-3" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4"
       cx="54.089516"
       cy="75.963257"
       rx="0.93767363"
       ry="0.89347595" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-5"
       cx="58.582962"
       cy="82.360382"
       rx="0.93767363"
       ry="0.89347595" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-2"
       cx="78.736336"
       cy="58.174397"
       rx="0.93767363"
       ry="0.89347595" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-2"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 70.685483,50.27378 -1.919264,-1.919264 -1.919262,-1.919261 0.199471,-0.199472 h 1.440738 l 1.198892,1.198377 1.919264,1.919261 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-19-6"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0607826"
       d="m 68.487166,46.235783 h -1.440738 -1.440864 v 1.30102 h 1.440864 1.440738 z" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-2-6"
       cx="65.628487"
       cy="46.886292"
       rx="0.93767363"
       ry="0.89347595" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-2-6-2"
       cx="64.354294"
       cy="40.318958"
       rx="0.93767363"
       ry="0.89347595" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-2-6-2-48"
       cx="84.65493"
       cy="36.344715"
       rx="0.93767363"
       ry="0.89347595" />
    <path
       d="M 69.919932,72.575773 H 59.021604 48.122334 v 1.301021 h 10.89927 10.898328 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.167173"
       id="rect1210-09-7-1-5-4-9-3-0-3-3" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-8"
       cx="47.713825"
       cy="73.22628"
       rx="0.93767363"
       ry="0.89347595" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.149545"
       d="m 104.50216,29.778798 h 8.72092 8.72167 v 1.301021 h -8.72167 -8.72092 z" />
    <path
       d="m 134.22155,60.260863 h 15.95922 15.96062 v 1.30102 h -15.96062 -15.95922 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.2023"
       id="rect1210-09-7-1-5-4-9-2-2" />
    <path
       d="m 132.38645,47.022775 h 10.89834 10.89927 v 1.301021 h -10.89927 -10.89834 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.167173"
       id="rect1210-09-7-1-5-4-9-3-0-6-3" />
    <path
       id="rect1210-09-7-1-5-4-9-3-0-5-3"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.171469"
       d="m 136.4069,75.854957 c 0,0 1.46305,1.463045 1.91927,1.919261 0.41529,0.41529 0.81803,0.818039 0.81803,0.818039 h 10.92647 11.46649 v 1.300694 h -11.46649 -10.02471 -1.44074 l -1.19889,-1.198375 -1.91927,-1.919263 z"
       sodipodi:nodetypes="cscccccccccc" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 122.05604,60.260864 1.91927,1.919264 1.91926,1.919261 -0.19947,0.199471 h -1.44074 l -1.19889,-1.198375 -1.91927,-1.919262 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-8-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 108.81683,78.972901 1.91927,1.919264 1.91926,1.919261 -0.19947,0.199471 h -1.44074 l -1.19889,-1.198375 -1.91927,-1.919262 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-45-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 135.72732,52.435906 0.16589,0.165881 1.91926,1.919264 -0.19947,0.199472 h -1.44074 l -1.1989,-1.198378 -0.72191,-0.721921 h 1.47587 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-5"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 132.02323,64.298859 1.91926,-1.919263 1.91927,-1.919261 -0.19948,-0.199472 h -1.44073 l -1.1989,1.198377 -1.91926,1.919261 z" />
    <path
       d="m 138.50443,44.2858 h 5.85687 5.85739 v 1.30102 h -5.85739 -5.85687 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.122553"
       id="rect1210-09-7-1-5-4-9-2-2-3" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-5-3"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 136.30611,48.323796 1.91927,-1.919263 1.91927,-1.919261 -0.19948,-0.199472 h -1.44073 l -1.1989,1.198377 -1.91927,1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-5-7"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 120.5523,83.010897 1.91926,-1.919263 1.91927,-1.919261 -0.19948,-0.199472 h -1.44073 l -1.1989,1.198377 -1.91926,1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-5-7-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 123.67045,79.893259 1.91926,-1.919263 1.91927,-1.919261 -0.19948,-0.199472 h -1.44073 l -1.1989,1.198377 -1.91925,1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-07"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 106.34235,29.778798 -1.07952,1.079521 -1.91925,1.919264 -0.91984,-0.920358 1.91926,-1.919264 0.15916,-0.159163 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-1-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.117346"
       d="m 105.04148,26.393658 h 5.36981 5.37028 v 1.301021 h -5.37028 -5.36981 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11-9"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.102557"
       d="m 111.01515,33.16736 h 4.1015 4.10186 v 1.301021 h -4.10186 -4.1015 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11-9-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.187254"
       d="m 116.48851,38.811846 h 13.67334 13.67455 v 1.301021 h -13.67455 -13.67334 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11-9-0-6"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.122158"
       d="m 121.68795,41.548822 h 5.81913 5.81962 v 1.301021 h -5.81962 -5.81913 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11-9-0-6-5"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.085961"
       d="m 124.42493,44.285798 h 2.88148 2.88173 v 1.301021 h -2.88173 -2.88148 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11-9-0-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.102557"
       d="m 124.10228,36.074866 h 4.1015 4.10186 v 1.301021 h -4.10186 -4.1015 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-1-8-3"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.117346"
       d="m 105.16813,29.778798 h 5.36981 5.37028 v 1.301021 h -5.37028 -5.36981 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-2-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834822"
       d="m 106.34224,26.393658 -1.07952,1.081011 -1.91926,1.92191 -0.91983,-0.921627 1.91925,-1.921911 0.15916,-0.159383 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-9-4"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 167.6007,58.262336 v 1.280543 h 0.55966 l -0.0997,0.09974 -1.91927,1.919264 -0.91984,-0.920359 1.91927,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-6-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 137.613,50.682526 0.19947,0.199469 -1.91926,1.919264 -0.45992,0.45992 v -0.460955 h -1.37925 l 0.91932,-0.919323 1.1989,-1.198375 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-1-5"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 130.18814,44.285798 1.91926,1.919264 1.91927,1.919261 -0.19947,0.199472 h -1.44075 l -1.19889,-1.198377 -1.91926,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 124.82618,49.313925 1.91926,1.919264 1.91927,1.919261 -0.19947,0.199472 h -1.44074 l -1.19889,-1.198377 -1.91927,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8-8-3"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 119.48963,38.811846 1.91926,1.919264 1.91927,1.919261 -0.19947,0.199472 h -1.44074 l -1.19889,-1.198377 -1.91927,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8-8-3-6"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 122.22661,41.548822 1.91926,1.919264 1.91927,1.919261 -0.19947,0.199472 h -1.44074 l -1.19889,-1.198377 -1.91927,-1.919261 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8-8-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 124.82626,54.787381 -0.91984,0.920359 1.8526,1.852599 a 1.4910613,1.4910613 0 0 1 1.00614,-0.834057 l -0.0196,-0.01963 z m 3.73879,3.73879 a 1.4910613,1.4910613 0 0 1 -0.10749,0.299207 h 0.008 l 0.19948,-0.199469 z" />
    <path
       d="m 127.02415,52.050824 h 4.20444 4.20481 v 1.301023 h -4.20481 -4.20444 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.103835"
       id="rect1210-09-7-1-5-4-9-3-0-2-2-1" />
    <path
       id="rect1210-09-7-1-5-4-9-3-0-2-2-1-0"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.223841"
       d="m 128.45995,57.524168 a 6.9292683,1.4910613 0 0 1 0.69404,0.650605 6.9292683,1.4910613 0 0 1 -0.69643,0.650605 h 12.8793 19.54107 v -1.30121 h -19.54107 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-7-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.171995"
       d="m 124.25436,62.997841 h 11.53609 11.53708 v 1.301018 h -11.53708 -11.53609 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-7-8-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.145556"
       d="m 154.75693,65.734223 h 8.26201 8.26272 v 1.301018 h -8.26272 -8.26201 z" />
    <path
       id="path11628-5"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.266049"
       d="m 146.69462,36.075483 a 3.38719,3.38719 0 0 1 3.38687,3.386873 3.38719,3.38719 0 0 1 -3.38687,3.387388 3.38719,3.38719 0 0 1 -3.38739,-3.387388 3.38719,3.38719 0 0 1 3.38739,-3.386873 z m 0,1.300694 a 2.0861702,2.0861702 0 0 0 -2.08618,2.086179 2.0861702,2.0861702 0 0 0 2.08618,2.086177 2.0861702,2.0861702 0 0 0 2.08566,-2.086177 2.0861702,2.0861702 0 0 0 -2.08566,-2.086179 z" />
    <path
       id="path11628-7-9"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.266049"
       d="m 124.72911,27.042276 a 3.38719,3.38719 0 0 1 3.38686,3.386873 3.38719,3.38719 0 0 1 -3.38686,3.387389 3.38719,3.38719 0 0 1 -3.3874,-3.387389 3.38719,3.38719 0 0 1 3.3874,-3.386873 z m 0,1.300694 a 2.0861702,2.0861702 0 0 0 -2.08619,2.086179 2.0861702,2.0861702 0 0 0 2.08619,2.086177 2.0861702,2.0861702 0 0 0 2.08565,-2.086177 2.0861702,2.0861702 0 0 0 -2.08565,-2.086179 z" />
    <path
       id="path11067-2-0"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 168.53859,56.153845 a 2.1086066,2.1086066 0 0 1 2.1084,2.108398 2.1086066,2.1086066 0 0 1 -2.1084,2.108914 2.1086066,2.1086066 0 0 1 -2.10892,-2.108914 2.1086066,2.1086066 0 0 1 2.10892,-2.108398 z m -0.0227,1.193725 a 0.91509383,0.91509383 0 0 0 -0.91519,0.914673 0.91509383,0.91509383 0 0 0 0.91519,0.915187 0.91509383,0.91509383 0 0 0 0.91519,-0.915188 0.91509383,0.91509383 0 0 0 -0.91519,-0.914672 z" />
    <path
       id="path11067-8-2"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 178.21444,51.961118 a 2.1086066,2.1086066 0 0 1 2.10839,2.108398 2.1086066,2.1086066 0 0 1 -2.10839,2.108914 2.1086066,2.1086066 0 0 1 -2.10892,-2.108914 2.1086066,2.1086066 0 0 1 2.10892,-2.108398 z m -0.0227,1.193726 a 0.91509383,0.91509383 0 0 0 -0.9152,0.914672 0.91509383,0.91509383 0 0 0 0.9152,0.915188 0.91509383,0.91509383 0 0 0 0.91518,-0.915188 0.91509383,0.91509383 0 0 0 -0.91518,-0.914672 z" />
    <path
       id="path11067-7-3"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 155.65115,45.564887 a 2.1086066,2.1086066 0 0 1 2.1084,2.108398 2.1086066,2.1086066 0 0 1 -2.1084,2.108914 2.1086066,2.1086066 0 0 1 -2.10891,-2.108914 2.1086066,2.1086066 0 0 1 2.10891,-2.108398 z m -0.0227,1.193725 a 0.91509383,0.91509383 0 0 0 -0.91519,0.914673 0.91509383,0.91509383 0 0 0 0.91519,0.915188 0.91509383,0.91509383 0 0 0 0.91519,-0.915188 0.91509383,0.91509383 0 0 0 -0.91519,-0.914673 z" />
    <path
       id="path11067-7-9-3-7"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 135.9673,30.314298 a 2.1086066,2.1086066 0 0 1 2.10839,2.108399 2.1086066,2.1086066 0 0 1 -2.10839,2.108913 2.1086066,2.1086066 0 0 1 -2.10893,-2.108913 2.1086066,2.1086066 0 0 1 2.10893,-2.108399 z m -0.0227,1.193726 a 0.91509383,0.91509383 0 0 0 -0.91519,0.914674 0.91509383,0.91509383 0 0 0 0.91519,0.915188 0.91509383,0.91509383 0 0 0 0.91519,-0.915188 0.91509383,0.91509383 0 0 0 -0.91519,-0.914674 z" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-5"
       cx="-161.33485"
       cy="79.253494"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-5-9"
       cx="-159.77167"
       cy="73.226578"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-5-2"
       cx="-134.61877"
       cy="82.360382"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-5-2-765"
       cx="-150.39995"
       cy="70.489601"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <path
       d="m 136.17238,53.419313 h 20.49485 20.49663 v 1.301021 h -20.49663 -20.49485 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.22925"
       id="rect1210-09-7-1-5-4-9-3-0-3-7" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-5-6"
       cx="-160.87794"
       cy="58.174774"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-2-6-5-2"
       cx="-133.44141"
       cy="42.199333"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-2-6-2-4"
       cx="-115.78157"
       cy="27.044168"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-2-6-2-4-99"
       cx="-119.21851"
       cy="33.817867"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-2-6-2-4-9"
       cx="-100.3372"
       cy="32.62236"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <path
       d="m 136.17237,50.682337 h 10.89833 10.89928 v 1.301021 H 147.0707 136.17237 Z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.167173"
       id="rect1210-09-7-1-5-4-9-3-0-3-3-7" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-45-4-8"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 128.15727,71.592664 0.16589,0.165881 1.91926,1.919264 -0.19947,0.199472 h -1.44074 l -1.1989,-1.198378 -0.72191,-0.721921 h 1.47587 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-6-0-1"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 130.04295,69.839284 0.19947,0.199469 -1.91926,1.919264 -0.45992,0.459919 v -0.460955 h -1.37925 l 0.91932,-0.919323 1.1989,-1.198375 z" />
    <path
       d="m 128.60233,72.576071 h 15.58399 15.58533 v 1.301021 h -15.58533 -15.58399 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.199906"
       id="rect1210-09-7-1-5-4-9-3-0-3-7-7" />
    <path
       d="m 128.60232,69.839095 h 10.89833 10.89928 v 1.301021 h -10.89928 -10.89833 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.167173"
       id="rect1210-09-7-1-5-4-9-3-0-3-3-7-4" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-8-6"
       cx="-158.37851"
       cy="51.332848"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-8-6-8"
       cx="-111.88845"
       cy="39.199757"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-8-6-8-4"
       cx="-114.2896"
       cy="36.781036"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-8-6-6"
       cx="-150.2187"
       cy="44.93631"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-6-4-8-6-6-4"
       cx="-130.19824"
       cy="49.964657"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-8-7"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 114.29019,78.972901 1.91927,1.919264 1.91926,1.919261 -0.19947,0.199471 h -1.44074 l -1.19889,-1.198375 -1.91927,-1.919262 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-8-7-7"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0834247"
       d="m 111.93499,82.090538 1.91927,1.919264 1.91926,1.919261 -0.19947,0.199471 h -1.44074 l -1.19889,-1.198375 -1.91927,-1.919262 z" />
    <path
       d="m 125.86877,75.855263 h 2.56749 2.56771 v 1.301022 h -2.56771 -2.56749 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.0811413"
       id="rect1210-09-7-1-5-4-9-3-0-2-01" />
    <path
       d="m 133.47617,75.854957 h 15.90619 15.90757 v 1.30102 h -15.90757 -15.90619 z"
       style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.201963"
       id="rect1210-09-7-1-5-4-9-3-0-2-0-5" />
    <path
       id="path11628-7-8-1"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.266049"
       d="m 167.37611,73.118594 a 3.38719,3.38719 0 0 1 3.38687,3.386873 3.38719,3.38719 0 0 1 -3.38687,3.387389 3.38719,3.38719 0 0 1 -3.38739,-3.387388 3.38719,3.38719 0 0 1 3.38739,-3.386874 z m 0,1.300694 a 2.0861702,2.0861702 0 0 0 -2.08618,2.086179 2.0861702,2.0861702 0 0 0 2.08618,2.086177 2.0861702,2.0861702 0 0 0 2.08566,-2.086177 2.0861702,2.0861702 0 0 0 -2.08566,-2.086179 z" />
    <path
       id="path11628-7-8-1-7"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.266049"
       d="m 174.13988,62.997858 a 3.38719,3.38719 0 0 1 3.38687,3.386873 3.38719,3.38719 0 0 1 -3.38687,3.387389 3.38719,3.38719 0 0 1 -3.38739,-3.387388 3.38719,3.38719 0 0 1 3.38739,-3.386874 z m 0,1.300694 a 2.0861702,2.0861702 0 0 0 -2.08618,2.086179 2.0861702,2.0861702 0 0 0 2.08618,2.086177 2.0861702,2.0861702 0 0 0 2.08566,-2.086177 2.0861702,2.0861702 0 0 0 -2.08566,-2.086179 z" />
    <path
       id="path11067-3"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 132.12094,74.397325 a 2.1086066,2.1086066 0 0 1 2.1084,2.108398 2.1086066,2.1086066 0 0 1 -2.1084,2.108914 2.1086066,2.1086066 0 0 1 -2.10892,-2.108914 2.1086066,2.1086066 0 0 1 2.10892,-2.108398 z m -0.0227,1.193726 a 0.91509383,0.91509383 0 0 0 -0.91519,0.914672 0.91509383,0.91509383 0 0 0 0.91519,0.915188 0.91509383,0.91509383 0 0 0 0.91519,-0.915188 0.91509383,0.91509383 0 0 0 -0.91519,-0.914672 z" />
    <path
       id="path11067-3-8"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 141.86677,83.369532 a 2.1086066,2.1086066 0 0 1 2.1084,2.108398 2.1086066,2.1086066 0 0 1 -2.1084,2.108914 2.1086066,2.1086066 0 0 1 -2.10892,-2.108914 2.1086066,2.1086066 0 0 1 2.10892,-2.108398 z m -0.0227,1.193726 a 0.91509383,0.91509383 0 0 0 -0.91519,0.914672 0.91509383,0.91509383 0 0 0 0.91519,0.915188 0.91509383,0.91509383 0 0 0 0.91519,-0.915188 0.91509383,0.91509383 0 0 0 -0.91519,-0.914672 z" />
    <path
       id="path11067-3-8-1"
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.261766"
       d="m 153.33527,64.276335 a 2.1086066,2.1086066 0 0 1 2.1084,2.108397 2.1086066,2.1086066 0 0 1 -2.1084,2.108914 2.1086066,2.1086066 0 0 1 -2.10892,-2.108914 2.1086066,2.1086066 0 0 1 2.10892,-2.108397 z m -0.0227,1.193726 a 0.91509383,0.91509383 0 0 0 -0.91519,0.914671 0.91509383,0.91509383 0 0 0 0.91519,0.915189 0.91509383,0.91509383 0 0 0 0.91519,-0.915189 0.91509383,0.91509383 0 0 0 -0.91519,-0.914671 z" />
    <ellipse
       style="fill:#ffcc00;fill-opacity:1;stroke-width:0.277951"
       id="path12153-5-2-76"
       cx="-147.32755"
       cy="63.64835"
       rx="0.93767363"
       ry="0.89347595"
       transform="scale(-1,1)" />
  </g>
</svg>

embark/static/content/images/bg.svg METASEP
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   width="10cm"
   height="10cm"
   viewBox="0 0 100 100"
   version="1.1"
   id="svg3491"
   inkscape:export-filename="C:\Users\z003xz3p\Desktop\bg.png"
   inkscape:export-xdpi="96"
   inkscape:export-ydpi="96"
   inkscape:version="1.1 (c68e22c387, 2021-05-23)"
   sodipodi:docname="bg.svg"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <sodipodi:namedview
     id="namedview3493"
     pagecolor="#505050"
     bordercolor="#eeeeee"
     borderopacity="1"
     inkscape:pageshadow="0"
     inkscape:pageopacity="0"
     inkscape:pagecheckerboard="0"
     inkscape:document-units="mm"
     showgrid="false"
     width="10mm"
     units="cm"
     inkscape:object-paths="true"
     inkscape:snap-intersection-paths="true"
     inkscape:snap-smooth-nodes="true"
     inkscape:snap-midpoints="true"
     inkscape:snap-object-midpoints="true"
     inkscape:snap-center="true"
     inkscape:snap-text-baseline="true"
     inkscape:snap-bbox="true"
     inkscape:bbox-paths="true"
     inkscape:snap-page="true"
     inkscape:zoom="1.4142136"
     inkscape:cx="335.52217"
     inkscape:cy="285.31759"
     inkscape:window-width="3440"
     inkscape:window-height="1387"
     inkscape:window-x="-8"
     inkscape:window-y="-8"
     inkscape:window-maximized="1"
     inkscape:current-layer="layer1" />
  <defs
     id="defs3488" />
  <g
     inkscape:label="Ebene 1"
     inkscape:groupmode="layer"
     id="layer1">
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-0-7"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -134.9912,95.371301 2.21763,2.217612 3.94265,3.942657 1.88959,-1.890658 -3.94265,-3.942655 -0.32696,-0.326956 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-0-3"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -130.72157,99.640912 2.21761,2.217608 3.94265,3.94268 1.88959,-1.89066 -3.94265,-3.942652 -0.32696,-0.326976 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-0-3-9"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -139.26079,91.10167 2.21761,2.217612 3.94265,3.942655 1.88959,-1.890636 -3.94265,-3.942675 -0.32696,-0.326956 z" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-0"
       cx="-181.41119"
       cy="197.64008"
       rx="1.9262234"
       ry="1.8354299" />
    <path
       id="rect1210-09-7-1-7-7-5-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.208602"
       d="m -142.14341,123.35017 h -8.26042 -8.26113 v 2.67263 h 8.26113 8.26042 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.307204"
       d="m -156.12151,110.49358 h -17.91499 -17.91658 v 2.67263 h 17.91658 17.91499 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-0"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.199569"
       d="m -131.21094,95.371301 h -7.56049 -7.56113 v 2.672631 h 7.56113 7.56049 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-0-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.209766"
       d="m -135.48055,91.10167 h -8.35284 -8.35355 v 2.672631 h 8.35355 8.35284 z" />
    <path
       id="rect1210-09-7-1-5-4"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.11865"
       d="m -69.50104,145.83754 h -2.672404 -2.672631 v 2.67263 h 2.672631 2.672404 z" />
    <path
       d="m -52.774882,123.35017 h -11.035124 -11.036069 v 2.67263 h 11.036069 11.035124 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.241104"
       id="rect1210-09-7-1-7-7-5" />
    <path
       d="m -63.81091,134.59384 h -5.517346 -5.517819 v 2.67266 h 5.517819 5.517346 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.170483"
       id="rect1210-09-7-1-0-2-2" />
    <path
       id="rect1210-09-7-1-0-8-5-8"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.418052"
       d="M -8.4906512,157.08123 H -41.666956 -74.846075 v 2.67263 h 33.179119 33.1763048 z" />
    <path
       id="rect1210-09-7-1-7-9-9-5"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.220282"
       d="m -56.422258,168.32492 h -9.211518 -9.212299 v 2.67263 h 9.212299 9.211518 z" />
    <path
       id="rect1210-09-7-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.294743"
       d="m -109.83107,80.54108 v 16.491356 16.492774 h 2.67265 V 97.032436 80.54108 Z" />
    <path
       id="rect1210-09-7-1-2"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.176576"
       d="m -115.45348,89.060929 v 5.918789 5.919302 h 2.67264 v -5.919302 -5.918789 z" />
    <path
       d="m -132.31843,108.18017 v 2.67241 2.67263 h 2.67263 v -2.67263 -2.67241 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.11865"
       id="rect1210-09-7-1-7" />
    <path
       d="m -154.52379,107.81353 v 6.14737 6.1479 h 2.67263 v -6.1479 -6.14737 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.179953"
       id="rect1210-09-7-1-7-3" />
    <path
       d="m -121.07474,108.18017 v 2.67241 2.67263 h 2.67264 v -2.67263 -2.67241 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.11865"
       id="rect1210-09-7-1-0" />
    <path
       id="rect1210-09-7-1-0-8"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.211227"
       d="m -98.587379,96.584936 v 8.469764 8.47051 h 2.672631 v -8.47051 -8.469764 z" />
    <path
       id="rect1210-09-7-1-0-8-3"
       style="fill:#171717;fill-opacity:1;stroke:none;stroke-width:0.525906"
       d="M 96.769531 -28.199219 L 96.769531 38.367188 L 96.769531 71.654297 L 96.769531 81.394531 L 96.769531 100.1875 A 6.9370578 6.9370578 0 0 0 94.884766 104.93945 A 6.9370578 6.9370578 0 0 0 101.82031 111.87695 A 6.9370578 6.9370578 0 0 0 108.75781 104.93945 A 6.9370578 6.9370578 0 0 0 106.87109 100.18359 L 106.87109 75.839844 L 113.94336 68.767578 L 128.8457 53.867188 L 133.5 49.214844 L 140.94922 41.763672 L 144.67578 38.039062 L 148.40039 34.3125 L 155.85156 26.863281 L 162.20117 20.513672 L 185.38281 20.513672 L 197.33008 32.458984 L 206.63867 41.763672 L 221.53906 56.666016 L 230.84766 65.970703 L 242.0332 65.970703 L 250.78125 65.970703 L 263.82227 65.970703 A 16.371483 16.371483 0 0 0 279.39062 77.289062 A 16.371483 16.371483 0 0 0 295.75977 60.919922 A 16.371483 16.371483 0 0 0 279.39062 44.544922 A 16.371483 16.371483 0 0 0 263.82227 55.869141 L 250.78125 55.869141 L 235.03125 55.869141 L 228.68164 49.519531 L 213.7793 34.619141 L 213.77734 34.621094 L 204.4707 25.314453 L 199.66992 20.513672 L 228.4043 20.513672 L 294.0293 20.513672 A 6.9370578 6.9370578 0 0 0 298.7832 22.400391 A 6.9370578 6.9370578 0 0 0 305.7207 15.462891 A 6.9370578 6.9370578 0 0 0 298.7832 8.5253906 A 6.9370578 6.9370578 0 0 0 294.03125 10.412109 L 228.4043 10.412109 L 189.57031 10.412109 L 169.20508 10.412109 L 158.01758 10.412109 L 148.70898 19.716797 L 133.80859 34.619141 L 126.35742 42.068359 L 111.45703 56.970703 L 106.87109 61.554688 L 106.87109 38.367188 L 106.87109 -28.199219 L 96.769531 -28.199219 z M 361.10938 -26.378906 L 351.80078 -17.076172 L 347 -12.275391 L 347 -16.167969 L 336.89844 -16.167969 L 336.89844 -4.1679688 L 336.89844 10.099609 L 336.89844 24.367188 L 336.89844 46.166016 L 336.89844 78.048828 L 326.67383 88.271484 L 263.15039 88.271484 L 199.625 88.271484 A 26.298563 26.298563 0 0 0 173.81641 67.023438 A 26.298563 26.298563 0 0 0 147.52148 93.322266 A 26.298563 26.298563 0 0 0 173.81641 119.61914 A 26.298563 26.298563 0 0 0 199.60938 98.373047 L 263.15039 98.373047 L 330.86133 98.373047 L 332.09766 97.136719 L 336.89844 92.335938 L 336.89844 104.30273 L 326.66211 114.55273 L 284.98242 114.55273 L 248.03906 114.55273 A 6.9370578 6.9370578 0 0 0 243.28711 112.66797 A 6.9370578 6.9370578 0 0 0 236.34961 119.60352 A 6.9370578 6.9370578 0 0 0 243.28711 126.54102 A 6.9370578 6.9370578 0 0 0 248.03906 124.6543 L 284.98242 124.6543 L 326.67383 124.6543 L 330.86133 124.6543 L 332.09766 123.41797 L 347 108.49609 L 347 46.166016 L 347 24.585938 L 351.79883 29.384766 L 358.14844 35.734375 L 358.14844 35.736328 L 358.14844 53.923828 L 358.14844 71.541016 A 6.9370578 6.9370578 0 0 0 356.26172 76.294922 A 6.9370578 6.9370578 0 0 0 363.19922 83.232422 A 6.9370578 6.9370578 0 0 0 370.13477 76.294922 A 6.9370578 6.9370578 0 0 0 368.25 71.541016 L 368.25 53.923828 L 368.25 45.839844 L 373.04688 50.636719 L 385.29297 62.882812 L 385.29297 55.416016 L 389.49805 55.416016 L 389.49805 52.802734 L 380.19336 43.494141 L 368.25 31.552734 L 367.54688 30.847656 L 365.29102 28.59375 L 358.94531 22.244141 L 347 10.298828 L 347 10.099609 L 347 2.0117188 L 358.94141 -9.9296875 L 366.92969 -17.917969 L 366.92969 -26.378906 L 361.10938 -26.378906 z M 75.519531 -22.898438 L 75.519531 39.429688 L 75.519531 61.011719 L 70.71875 56.210938 L 64.371094 49.861328 L 64.369141 49.861328 L 64.369141 32.896484 L 64.369141 14.054688 A 6.9370578 6.9370578 0 0 0 66.255859 9.3007812 A 6.9370578 6.9370578 0 0 0 59.318359 2.3652344 A 6.9370578 6.9370578 0 0 0 52.382812 9.3007812 A 6.9370578 6.9370578 0 0 0 54.267578 14.052734 L 54.267578 32.896484 L 54.267578 39.755859 L 49.470703 34.958984 L 41.917969 27.40625 A 16.371483 16.371483 0 0 0 43.658203 20.058594 A 16.371483 16.371483 0 0 0 27.285156 3.6894531 A 16.371483 16.371483 0 0 0 10.916016 20.058594 A 16.371483 16.371483 0 0 0 27.285156 36.431641 A 16.371483 16.371483 0 0 0 34.820312 34.59375 L 42.324219 42.101562 L 54.267578 54.042969 L 54.267578 54.044922 L 63.572266 63.351562 L 75.519531 75.298828 L 75.519531 97.580078 L 70.490234 102.60742 L 58.544922 114.55273 L 20.912109 114.55273 L -20.912109 114.55273 L -20.912109 124.6543 L 20.912109 124.6543 L 62.730469 124.6543 L 77.630859 109.75391 L 85.619141 101.76562 L 85.619141 93.302734 L 85.619141 39.429688 L 85.619141 -22.898438 L 75.519531 -22.898438 z M 27.460938 12.957031 A 7.1049017 7.1049017 0 0 1 34.566406 20.058594 A 7.1049017 7.1049017 0 0 1 27.460938 27.164062 A 7.1049017 7.1049017 0 0 1 20.355469 20.058594 A 7.1049017 7.1049017 0 0 1 27.460938 12.957031 z M -12.660156 28.59375 L -19.806641 35.734375 L -4.9042969 50.636719 L 2.6523438 58.195312 A 16.371483 16.371483 0 0 0 0.90625 65.537109 A 16.371483 16.371483 0 0 0 17.279297 81.908203 A 16.371483 16.371483 0 0 0 33.650391 65.537109 A 16.371483 16.371483 0 0 0 17.279297 49.164062 A 16.371483 16.371483 0 0 0 9.7519531 51.009766 L 2.2402344 43.494141 L -12.660156 28.59375 z M 279.21484 53.814453 A 7.1049017 7.1049017 0 0 1 286.32031 60.919922 A 7.1049017 7.1049017 0 0 1 279.21484 68.021484 A 7.1049017 7.1049017 0 0 1 272.10938 60.919922 A 7.1049017 7.1049017 0 0 1 279.21484 53.814453 z M 17.103516 58.431641 A 7.1049017 7.1049017 0 0 1 24.208984 65.537109 A 7.1049017 7.1049017 0 0 1 17.103516 72.638672 A 7.1049017 7.1049017 0 0 1 9.9980469 65.537109 A 7.1049017 7.1049017 0 0 1 17.103516 58.431641 z M 173.81641 77.125 A 16.197284 16.197284 0 0 1 190.01367 93.322266 A 16.197284 16.197284 0 0 1 173.81641 109.51953 A 16.197284 16.197284 0 0 1 157.62305 93.322266 A 16.197284 16.197284 0 0 1 173.81641 77.125 z M 363.69922 114.55273 L 348.79688 129.45508 L 339.93945 138.3125 A 6.9370578 6.9370578 0 0 0 333.26758 145.24023 A 6.9370578 6.9370578 0 0 0 340.20508 152.17773 A 6.9370578 6.9370578 0 0 0 347.13477 145.40039 L 355.93945 136.60156 L 367.88477 124.6543 L 379.82812 124.6543 L 395.95898 124.6543 L 395.95898 114.55273 L 379.82812 114.55273 L 363.69922 114.55273 z M 135.90234 123.48438 A 6.9370578 6.9370578 0 0 0 131.14844 125.37109 L 121.58008 125.37109 L 107.25977 125.37109 L 107.25781 125.36914 L 107.25586 125.37109 L 92.355469 140.27148 L 86.007812 146.61914 L 70.257812 146.61914 L 57.216797 146.61914 A 16.371483 16.371483 0 0 0 41.648438 135.29883 A 16.371483 16.371483 0 0 0 26.087891 146.61719 L 21.03125 146.61719 L 15.689453 146.61719 A 6.9370578 6.9370578 0 0 0 10.933594 144.73047 A 6.9370578 6.9370578 0 0 0 3.9980469 151.66797 A 6.9370578 6.9370578 0 0 0 10.933594 158.60547 A 6.9370578 6.9370578 0 0 0 15.685547 156.71875 L 21.03125 156.71875 L 26.083984 156.71875 A 16.371483 16.371483 0 0 0 41.648438 168.04297 A 16.371483 16.371483 0 0 0 57.224609 156.7207 L 70.257812 156.7207 L 90.191406 156.7207 L 90.191406 156.71875 L 99.498047 147.41602 L 111.44141 135.47266 L 121.58008 135.47266 L 131.14844 135.47266 A 6.9370578 6.9370578 0 0 0 135.90234 137.35938 A 6.9370578 6.9370578 0 0 0 142.83789 130.42188 A 6.9370578 6.9370578 0 0 0 135.90234 123.48438 z M 272.67383 134.6543 A 6.9370578 6.9370578 0 0 0 265.73633 141.5918 A 6.9370578 6.9370578 0 0 0 272.67383 148.5293 A 6.9370578 6.9370578 0 0 0 277.42773 146.64258 L 283.68359 146.64258 L 290.68555 146.64258 L 297.03516 152.99219 L 301.83398 157.79102 L 252.3418 157.79102 L 140.63086 157.79102 L 125.72852 172.69336 L 119.38086 179.04102 L 38.945312 179.04102 L -45.677734 179.04102 L -45.677734 189.14258 L 38.945312 189.14258 L 123.5625 189.14258 L 132.87109 179.83789 L 144.81641 167.89258 L 252.3418 167.89258 L 316.02148 167.89258 L 311.2207 172.69336 L 304.87305 179.04102 L 234.5332 179.04102 L 161.58789 179.04102 A 6.9370578 6.9370578 0 0 0 156.83398 177.15625 A 6.9370578 6.9370578 0 0 0 149.89844 184.0918 A 6.9370578 6.9370578 0 0 0 156.83398 191.0293 A 6.9370578 6.9370578 0 0 0 161.58789 189.14258 L 234.5332 189.14258 L 247.23438 189.14258 C 245.11255 191.26441 243.5763 192.80061 242.43555 193.94141 C 239.21119 197.16576 236.08398 200.29297 236.08398 200.29297 L 151.25 200.29297 L 68.457031 200.29297 A 6.9370578 6.9370578 0 0 0 63.792969 198.49023 A 6.9370578 6.9370578 0 0 0 56.855469 205.42773 A 6.9370578 6.9370578 0 0 0 63.792969 212.36523 A 6.9370578 6.9370578 0 0 0 68.632812 210.39258 L 151.25 210.39258 L 229.08203 210.39258 L 240.26758 210.39258 L 249.57617 201.08789 L 261.52344 189.14258 L 309.05273 189.14258 L 318.36133 179.83789 L 330.30859 167.89258 L 359.29102 167.89258 A 6.9370578 6.9370578 0 0 0 364.04492 169.7793 A 6.9370578 6.9370578 0 0 0 370.98242 162.8418 A 6.9370578 6.9370578 0 0 0 364.04492 155.9043 A 6.9370578 6.9370578 0 0 0 359.29297 157.79102 L 316.12305 157.79102 L 304.17773 145.8457 L 294.86914 136.54102 L 283.68359 136.54102 L 277.42773 136.54102 A 6.9370578 6.9370578 0 0 0 272.67383 134.6543 z M 41.826172 144.56836 A 7.1049017 7.1049017 0 0 1 48.931641 151.66992 A 7.1049017 7.1049017 0 0 1 41.826172 158.77539 A 7.1049017 7.1049017 0 0 1 34.720703 151.66992 A 7.1049017 7.1049017 0 0 1 41.826172 144.56836 z M 341.16797 179.04102 L 341.16797 179.04297 C 341.16499 179.04595 329.80917 190.39969 326.26758 193.94141 L 319.91602 200.29297 L 298.4668 200.29297 L 285.51367 200.29297 L 284.17969 200.29297 L 281.0957 203.37695 A 16.371483 16.371483 0 0 0 273.75195 201.62891 A 16.371483 16.371483 0 0 0 257.37891 217.99805 A 16.371483 16.371483 0 0 0 273.75195 234.37109 A 16.371483 16.371483 0 0 0 290.12109 217.99805 A 16.371483 16.371483 0 0 0 288.2793 210.48047 L 288.36719 210.39258 L 312.91602 210.39258 L 324.10156 210.39258 L 333.41016 201.08789 L 345.35547 189.14258 L 367.625 189.14258 L 394.08203 189.14258 L 394.08203 179.04102 L 367.625 179.04102 L 341.16797 179.04102 z M 35.390625 198.4082 A 6.9370578 6.9370578 0 0 0 30.636719 200.29492 L 30.339844 200.29492 L 25.292969 200.29492 L 25.291016 200.29297 L 25.289062 200.29492 L 10.388672 215.19336 L 4.0390625 221.54297 L 2.6289062 221.54297 L -2.9628906 221.54297 L -2.9628906 228.54688 L -4.5117188 230.0957 L -2.9628906 231.64453 L 2.6289062 231.64453 L 8.2226562 231.64453 L 17.53125 222.33984 L 29.474609 210.39648 L 30.339844 210.39648 L 30.638672 210.39648 A 6.9370578 6.9370578 0 0 0 35.390625 212.28125 A 6.9370578 6.9370578 0 0 0 42.328125 205.3457 A 6.9370578 6.9370578 0 0 0 35.390625 198.4082 z M 273.57617 210.89648 A 7.1049017 7.1049017 0 0 1 280.68164 217.99805 A 7.1049017 7.1049017 0 0 1 273.57617 225.10352 A 7.1049017 7.1049017 0 0 1 266.4707 217.99805 A 7.1049017 7.1049017 0 0 1 273.57617 210.89648 z M 205.74414 219.65625 A 6.9370578 6.9370578 0 0 0 198.80664 226.59375 A 6.9370578 6.9370578 0 0 0 205.74414 233.53125 A 6.9370578 6.9370578 0 0 0 210.49609 231.64453 L 215.84375 231.64453 L 221.75781 231.64453 L 233.70508 243.58984 L 243.01367 252.89453 L 254.19922 252.89453 L 332.58008 252.89453 L 422.15625 252.89453 L 422.15625 242.79297 L 332.58008 242.79297 L 313.43164 242.79297 L 318.23242 237.99219 L 324.58008 231.64453 L 358.76758 231.64453 L 397.13867 231.64453 L 397.13867 221.54297 L 358.76758 221.54297 L 320.40039 221.54297 L 311.0918 230.84766 L 299.14453 242.79297 L 247.19336 242.79297 L 240.8457 236.44531 L 225.94531 221.54297 L 215.84375 221.54297 L 210.49805 221.54297 A 6.9370578 6.9370578 0 0 0 205.74414 219.65625 z M 161.2793 221.44922 A 16.371483 16.371483 0 0 0 144.90625 237.81836 A 16.371483 16.371483 0 0 0 147.76953 247.07031 L 140.60938 254.22852 L 128.66406 266.17383 L 101.00195 266.17383 L 80.34375 266.17383 L 69.158203 266.17383 L 59.849609 275.47852 L 47.90625 287.42383 L 21.230469 287.42383 L 10.042969 287.42383 L 0.73632812 296.72852 L -14.166016 311.63086 L -7.0234375 318.77539 L 7.8769531 303.875 L 14.226562 297.52539 L 29.158203 297.52539 L 41.105469 309.47266 L 47.455078 315.81836 L 47.453125 315.82031 L 62.355469 330.72266 L 71.664062 340.02539 L 94.035156 340.02539 L 112.22266 340.02539 L 124.16992 351.97266 L 133.47656 361.27734 L 144.66406 361.27734 L 220.18555 361.27734 L 298.53906 361.27734 A 16.371483 16.371483 0 0 0 314.10742 372.59961 A 16.371483 16.371483 0 0 0 330.47656 356.22656 A 16.371483 16.371483 0 0 0 314.10742 339.85547 A 16.371483 16.371483 0 0 0 298.53906 351.17578 L 220.18555 351.17578 L 174.01172 351.17578 L 178.8125 346.375 L 185.16211 340.02539 L 226.45312 340.02539 L 267.17773 340.02539 A 6.9370578 6.9370578 0 0 0 271.93164 341.91211 A 6.9370578 6.9370578 0 0 0 278.86914 334.97656 A 6.9370578 6.9370578 0 0 0 271.93164 328.03906 A 6.9370578 6.9370578 0 0 0 267.17773 329.92578 L 226.45312 329.92578 L 192.16406 329.92578 L 180.97852 329.92578 L 171.66992 339.22852 L 159.72266 351.17578 L 137.66016 351.17578 L 131.31055 344.82617 L 116.41016 329.92578 L 94.035156 329.92578 L 75.845703 329.92578 L 69.496094 323.57617 L 64.695312 318.77539 L 95.59375 318.77539 L 136.91406 318.77539 A 6.9370578 6.9370578 0 0 0 141.66797 320.66211 A 6.9370578 6.9370578 0 0 0 148.60547 313.72461 A 6.9370578 6.9370578 0 0 0 141.66797 306.78906 A 6.9370578 6.9370578 0 0 0 136.91602 308.67383 L 95.59375 308.67383 L 54.59375 308.67383 L 48.246094 302.32617 L 43.445312 297.52539 L 116.20508 297.52539 L 218.77539 297.52539 A 26.298563 26.298563 0 0 0 244.56836 318.77539 A 26.298563 26.298563 0 0 0 270.86328 292.47461 A 26.298563 26.298563 0 0 0 244.56836 266.17969 A 26.298563 26.298563 0 0 0 218.77734 287.42383 L 116.20508 287.42383 L 62.193359 287.42383 L 66.992188 282.625 L 73.341797 276.27539 L 101.00195 276.27539 L 132.84961 276.27539 L 147.75195 261.375 L 155.86719 253.25977 A 16.371483 16.371483 0 0 0 161.2793 254.19141 A 16.371483 16.371483 0 0 0 177.64844 237.81836 A 16.371483 16.371483 0 0 0 161.2793 221.44922 z M 161.10352 230.7168 A 7.1049017 7.1049017 0 0 1 168.20898 237.81836 A 7.1049017 7.1049017 0 0 1 161.10352 244.92383 A 7.1049017 7.1049017 0 0 1 153.99805 237.81836 A 7.1049017 7.1049017 0 0 1 161.10352 230.7168 z M 109.38672 240.90625 A 6.9370578 6.9370578 0 0 0 104.63281 242.79297 L 36.460938 242.79297 L -36.460938 242.79297 L -36.460938 252.89453 L 36.460938 252.89453 L 104.63281 252.89453 A 6.9370578 6.9370578 0 0 0 109.38672 254.78125 A 6.9370578 6.9370578 0 0 0 116.32422 247.84375 A 6.9370578 6.9370578 0 0 0 109.38672 240.90625 z M 244.56836 276.27734 A 16.197284 16.197284 0 0 1 260.76172 292.47461 A 16.197284 16.197284 0 0 1 244.56836 308.67188 A 16.197284 16.197284 0 0 1 228.37109 292.47461 A 16.197284 16.197284 0 0 1 244.56836 276.27734 z M 387.99609 287.42383 L 378.6875 296.72852 L 366.74219 308.67578 L 339.83984 308.67578 L 317.03516 308.67578 A 16.371483 16.371483 0 0 0 301.4668 297.35547 A 16.371483 16.371483 0 0 0 285.09766 313.72461 A 16.371483 16.371483 0 0 0 301.4668 330.09961 A 16.371483 16.371483 0 0 0 317.04297 318.77539 L 339.83984 318.77539 L 370.92773 318.77539 L 385.83008 303.875 L 400.73047 288.97266 L 399.18359 287.42383 L 387.99609 287.42383 z M 301.64258 306.62305 A 7.1049017 7.1049017 0 0 1 308.74805 313.72461 A 7.1049017 7.1049017 0 0 1 301.64258 320.83008 A 7.1049017 7.1049017 0 0 1 294.53711 313.72461 A 7.1049017 7.1049017 0 0 1 301.64258 306.62305 z M 32.75 339.86914 A 16.371483 16.371483 0 0 0 16.378906 356.23828 A 16.371483 16.371483 0 0 0 32.75 372.61328 A 16.371483 16.371483 0 0 0 48.324219 361.28906 L 55.195312 361.28906 L 65.294922 361.28906 L 75.519531 371.51172 L 75.519531 377.42578 L 75.519531 387.52734 L 85.619141 387.52734 L 85.619141 377.42578 L 85.619141 367.32617 L 70.71875 352.42383 L 69.482422 351.18945 L 55.195312 351.18945 L 48.318359 351.18945 A 16.371483 16.371483 0 0 0 32.75 339.86914 z M 313.93164 349.125 A 7.1049017 7.1049017 0 0 1 321.03711 356.22656 A 7.1049017 7.1049017 0 0 1 313.93164 363.33203 A 7.1049017 7.1049017 0 0 1 306.82617 356.22656 A 7.1049017 7.1049017 0 0 1 313.93164 349.125 z M 32.925781 349.13672 A 7.1049017 7.1049017 0 0 1 40.03125 356.23828 A 7.1049017 7.1049017 0 0 1 32.925781 363.3457 A 7.1049017 7.1049017 0 0 1 25.820312 356.23828 A 7.1049017 7.1049017 0 0 1 32.925781 349.13672 z M 363.48438 349.28906 A 6.9370578 6.9370578 0 0 0 356.55078 356.12891 L 351.80078 360.87695 L 336.89844 375.7793 L 336.89844 385.88086 L 336.89844 395.98047 L 347 395.98047 L 347 385.88086 L 347 379.96484 L 358.94141 368.02344 L 363.81445 363.15039 A 6.9370578 6.9370578 0 0 0 370.41992 356.22656 A 6.9370578 6.9370578 0 0 0 363.48438 349.28906 z M 101.82031 357.12305 A 6.9370578 6.9370578 0 0 0 94.884766 364.06055 A 6.9370578 6.9370578 0 0 0 96.769531 368.8125 L 96.769531 377.95312 L 96.769531 391.8457 L 106.87109 391.8457 L 106.87109 377.95312 L 106.87109 368.81445 A 6.9370578 6.9370578 0 0 0 108.75781 364.06055 A 6.9370578 6.9370578 0 0 0 101.82031 357.12305 z "
       transform="scale(0.26458333)" />
    <path
       id="rect1210-09-7-1-7-9"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.11865"
       d="m -87.343686,108.18017 v 2.67241 2.67263 h 2.67263 v -2.67263 -2.67241 z" />
    <path
       d="m -109.83107,180.82251 v 2.67241 2.67263 h 2.67265 v -2.67263 -2.67241 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.11865"
       id="rect1210-09-7-1-5" />
    <path
       id="rect1210-09-7-1-7-7"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.11865"
       d="m -132.31843,180.82251 v 2.67241 2.67263 h 2.67263 v -2.67263 -2.67241 z" />
    <path
       id="rect1210-09-7-1-0-2"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.11865"
       d="m -121.07474,180.82251 v 2.67241 2.67263 h 2.67264 v -2.67263 -2.67241 z" />
    <path
       d="m -98.587379,180.82251 v 6.1809 6.18141 h 2.672631 v -6.18141 -6.1809 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.180443"
       id="rect1210-09-7-1-0-8-5" />
    <path
       d="m -87.343686,180.82251 v 2.67241 2.67263 h 2.67263 v -2.67263 -2.67241 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.11865"
       id="rect1210-09-7-1-7-9-9" />
    <path
       d="m -142.14341,145.83754 h -32.78435 -32.78721 v 2.67263 h 32.78721 32.78435 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.415576"
       id="rect1210-09-7-1-5-4-9" />
    <path
       d="m -158.41605,183.49492 h -32.78437 -32.78721 v 2.67263 h 32.78721 32.78437 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.415576"
       id="rect1210-09-7-1-5-4-9-2" />
    <path
       d="m -157.81035,151.46 h -22.38796 -22.38991 v 2.67263 h 22.38991 22.38796 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.343416"
       id="rect1210-09-7-1-5-4-9-3-0" />
    <path
       d="m -212.23088,151.46 h -22.38796 -22.38991 v 2.67263 h 22.38991 22.38796 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.343416"
       id="rect1210-09-7-1-5-4-9-3-0-6" />
    <path
       id="rect1210-09-7-1-5-4-9-3-0-5"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.352242"
       d="m -176.83641,151.45977 c 0,0 -3.00549,3.00548 -3.94266,3.94268 -0.85311,0.85311 -1.68046,1.68044 -1.68046,1.68044 h -22.44577 -23.5551 v 2.67198 h 23.5551 20.59334 2.95965 l 2.46283,-2.46179 3.94265,-3.94265 z"
       sodipodi:nodetypes="cscccccccccc" />
    <path
       d="m -142.14341,157.08123 h -5.57526 -5.57577 v 2.67263 h 5.57577 5.57526 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       id="rect1210-09-7-1-5-4-9-6" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -153.29405,157.08078 -3.94265,3.94265 -3.94265,3.94266 0.40976,0.40978 h 2.95965 l 2.46283,-2.46178 3.94265,-3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -131.53539,184.2769 -3.94265,3.94265 -3.94266,3.94268 0.40974,0.40976 h 2.95966 l 2.46285,-2.46178 3.94265,-3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-81"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -96.69779,98.475593 3.942655,-3.942676 3.942654,-3.942654 -0.409742,-0.409763 h -2.959651 l -2.46285,2.461781 -3.942655,3.942655 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-81-7"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -85.454098,110.07083 3.942655,-3.94268 3.942655,-3.94265 -0.409742,-0.40976 h -2.959652 l -2.462849,2.46178 -3.942655,3.94265 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-81-7-2"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -86.017375,96.313569 -1.372614,1.371545 -3.942655,3.942676 1.889588,1.89064 3.942655,-3.942659 1.675165,-1.675144 h -2.192139 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-81-7-3"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -69.813451,104.44837 3.942655,-3.94268 3.942654,-3.942652 -0.409742,-0.409763 h -2.959651 l -2.46285,2.461781 -3.942655,3.942654 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-81-7-3-4"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -48.445719,98.825905 3.942655,-3.942675 3.942655,-3.942655 -0.409743,-0.409763 h -2.959651 l -2.46285,2.461782 -3.942654,3.942655 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -120.29204,184.27708 -3.94266,3.94266 -2.11358,2.11358 v 2.23883 h 1.54034 l 2.46283,-2.46178 3.94266,-3.94265 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-0"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -109.04801,184.2769 -3.94265,3.94265 -3.94267,3.94268 0.40976,0.40976 h 2.95964 l 2.46283,-2.46178 3.94268,-3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-0-4"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -115.4535,190.68133 -3.94266,3.94266 -3.94268,3.94265 0.40977,0.40979 h 2.95965 l 2.46283,-2.46179 3.94266,-3.94265 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -153.29444,145.83754 -3.94265,3.94267 -3.94266,3.94266 0.40977,0.40976 h 2.95963 l 2.46285,-2.46178 3.94265,-3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-00"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -107.15842,105.41492 -3.94266,-3.94266 -3.94264,-3.942653 -0.40976,0.409763 v 2.95965 l 2.46175,2.46283 3.94265,3.94265 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-00-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -119.96295,92.60968 v 1.97558 h -1.11253 v 0.691073 l 2.46178,2.462829 3.94265,3.942678 1.89066,-1.889591 -3.94267,-3.942655 z" />
    <path
       d="m -174.92248,140.21631 h -22.38798 -22.38991 v 2.67263 h 22.38991 22.38798 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.343416"
       id="rect1210-09-7-1-5-4-9-3-0-35" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-9"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -170.40659,134.59386 -3.94265,3.94266 -3.94266,3.94265 0.40977,0.40977 h 2.95965 l 2.46283,-2.46178 3.94265,-3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-45"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -175.6878,174.73782 -0.34076,0.34076 -3.94265,3.94268 0.40976,0.40976 h 2.95965 l 2.46283,-2.46178 1.48301,-1.48301 h -3.03184 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -153.90013,191.79002 -3.94268,-3.94268 -3.94265,-3.94265 0.40976,-0.40977 h 2.95965 l 2.46283,2.46178 3.94265,3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -148.10248,148.51017 -3.94268,-3.94266 -3.94265,-3.94265 0.40976,-0.40977 h 2.95965 l 2.46283,2.46177 3.94265,3.94265 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -154.52333,117.72742 v 2.38109 l 1.97132,1.97135 3.94266,3.94265 1.88959,-1.89066 -3.94266,-3.94265 -2.46283,-2.46178 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -159.90124,110.4939 2.21762,2.21762 3.94265,3.94265 1.88961,-1.89066 -3.94268,-3.94265 -0.32695,-0.32696 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.284847"
       d="m -157.22894,103.53541 h -15.40251 -15.40385 v 2.67263 h 15.40385 15.40251 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-1-2"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.141116"
       d="m -133.91543,103.91054 h -3.78022 -3.78058 v 2.67263 h 3.78058 3.78022 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-2"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171494"
       d="m -159.90103,103.53541 2.21761,2.22068 3.94266,3.9481 1.8896,-1.89327 -3.94267,-3.9481 -0.32696,-0.32741 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-0"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -137.69565,103.91054 2.21761,2.21761 3.94265,3.94266 1.88959,-1.89064 -3.94265,-3.94267 -0.32698,-0.32696 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-0-58"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -126.45196,103.91054 2.21761,2.21761 3.94265,3.94266 1.8896,-1.89064 -3.94266,-3.94267 -0.32696,-0.32696 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-9"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -226.9859,179.38961 v 2.63058 h -1.14968 l 0.20481,0.20481 3.94265,3.94266 1.88959,-1.89066 -3.94265,-3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-6"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -179.56145,171.13593 -0.40976,0.40976 3.94265,3.94265 0.9448,0.9448 v -0.94691 h 2.83331 l -1.88852,-1.88852 -2.46283,-2.46178 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -207.71497,145.83754 -3.94267,3.94267 -3.94266,3.94266 0.40977,0.40976 h 2.95965 l 2.46283,-2.46178 3.94267,-3.94266 z" />
    <path
       d="m -157.80924,162.70308 h -5.27427 -5.27474 v 2.67263 h 5.27474 5.27427 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.166685"
       id="rect1210-09-7-1-5-4-9-3-0-2" />
    <path
       d="m -142.14341,168.32492 h -5.57526 -5.57577 v 2.67263 h 5.57577 5.57526 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       id="rect1210-09-7-1-5-4-9-6-4" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -153.29405,168.32447 -3.94265,3.94267 -3.94265,3.94266 0.40976,0.40976 h 2.95965 l 2.46283,-2.46178 3.94265,-3.94265 z" />
    <path
       d="m -157.80924,173.94677 h -8.63701 -8.63776 v 2.67263 h 8.63776 8.63701 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.213304"
       id="rect1210-09-7-1-5-4-9-3-0-2-2" />
    <path
       d="m -173.43678,162.70244 h -30.71957 -30.72221 v 2.67263 h 30.72221 30.71957 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.402275"
       id="rect1210-09-7-1-5-4-9-3-0-2-0" />
    <path
       d="m -119.96942,196.3038 h -30.71956 -30.72221 v 2.67263 h 30.72221 30.71956 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.402275"
       id="rect1210-09-7-1-5-4-9-3-0-2-0-2" />
    <path
       d="M -30.731361,196.30377 H -58.252684 -85.77637 v 2.67264 h 27.523686 27.521323 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.380759"
       id="rect1210-09-7-1-5-4-9-3-0-2-0-2-6" />
    <path
       d="m -43.693954,189.89936 h -18.621312 -18.622916 v 2.67263 h 18.622916 18.621312 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.313198"
       id="rect1210-09-7-1-5-4-9-3-0-2-0-2-6-1" />
    <path
       id="rect1210-09-7-1-0-2-2-0"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.427253"
       d="m -142.14341,134.59386 h -34.65299 -34.656 v 2.67264 h 34.656 34.65299 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-19"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.124863"
       d="m -152.6184,140.21509 h -2.95965 -2.9599 v 2.67263 h 2.9599 2.95965 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-7"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.410048"
       d="m -136.0513,189.89936 h -31.91851 -31.92129 v 2.67263 h 31.92129 31.91851 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-9"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.310856"
       d="m -169.63626,123.35017 h -18.34364 -18.34522 v 2.67263 h 18.34522 18.34364 z" />
    <path
       id="path11628"
       style="fill:#250539;fill-opacity:1;stroke-width:0.546533"
       d="m -164.26473,117.72896 a 6.9581615,6.9581615 0 0 0 -6.95752,6.95752 6.9581615,6.9581615 0 0 0 6.95752,6.95858 6.9581615,6.9581615 0 0 0 6.95858,-6.95858 6.9581615,6.9581615 0 0 0 -6.95858,-6.95752 z m 0,2.67197 a 4.2855314,4.2855314 0 0 1 4.28553,4.28555 4.2855314,4.2855314 0 0 1 -4.28553,4.28554 4.2855314,4.2855314 0 0 1 -4.28448,-4.28554 4.2855314,4.2855314 0 0 1 4.28448,-4.28555 z" />
    <path
       id="path11628-7"
       style="fill:#250539;fill-opacity:1;stroke-width:0.546533"
       d="m -197.67234,104.87239 a 6.9581615,6.9581615 0 0 0 -6.95752,6.9575 6.9581615,6.9581615 0 0 0 6.95752,6.95857 6.9581615,6.9581615 0 0 0 6.95857,-6.95857 6.9581615,6.9581615 0 0 0 -6.95857,-6.9575 z m 0,2.67195 a 4.2855314,4.2855314 0 0 1 4.28553,4.28555 4.2855314,4.2855314 0 0 1 -4.28553,4.28555 4.2855314,4.2855314 0 0 1 -4.28448,-4.28555 4.2855314,4.2855314 0 0 1 4.28448,-4.28555 z" />
    <path
       id="path11628-7-97"
       style="fill:#250539;fill-opacity:1;stroke-width:0.546533"
       d="m -157.68188,85.480481 a 6.9581615,6.9581615 0 0 0 -6.95752,6.957504 6.9581615,6.9581615 0 0 0 6.95752,6.958573 6.9581615,6.9581615 0 0 0 6.95855,-6.958573 6.9581615,6.9581615 0 0 0 -6.95855,-6.957504 z m 0,2.671953 a 4.2855314,4.2855314 0 0 1 4.28553,4.285551 4.2855314,4.2855314 0 0 1 -4.28553,4.285552 4.2855314,4.2855314 0 0 1 -4.2845,-4.285552 4.2855314,4.2855314 0 0 1 4.2845,-4.285551 z" />
    <path
       id="path11628-7-8"
       style="fill:#250539;fill-opacity:1;stroke-width:0.546533"
       d="m -240.75008,157.08125 a 6.9581615,6.9581615 0 0 0 -6.95751,6.9575 6.9581615,6.9581615 0 0 0 6.95751,6.95858 6.9581615,6.9581615 0 0 0 6.95857,-6.95858 6.9581615,6.9581615 0 0 0 -6.95857,-6.9575 z m 0,2.67195 a 4.2855314,4.2855314 0 0 1 4.28555,4.28555 4.2855314,4.2855314 0 0 1 -4.28555,4.28556 4.2855314,4.2855314 0 0 1 -4.28448,-4.28556 4.2855314,4.2855314 0 0 1 4.28448,-4.28555 z" />
    <path
       id="path11628-7-8-4"
       style="fill:#250539;fill-opacity:1;stroke-width:0.546533"
       d="m -246.09571,123.35142 a 6.9581615,6.9581615 0 0 0 -6.95753,6.95751 6.9581615,6.9581615 0 0 0 6.95753,6.95857 6.9581615,6.9581615 0 0 0 6.95857,-6.95857 6.9581615,6.9581615 0 0 0 -6.95857,-6.95751 z m 0,2.67195 a 4.2855314,4.2855314 0 0 1 4.28553,4.28556 4.2855314,4.2855314 0 0 1 -4.28553,4.28555 4.2855314,4.2855314 0 0 1 -4.28449,-4.28555 4.2855314,4.2855314 0 0 1 4.28449,-4.28556 z" />
    <path
       id="path11067"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -170.65279,159.70809 a 4.3316216,4.3316216 0 0 0 -4.3312,4.3312 4.3316216,4.3316216 0 0 0 4.3312,4.33224 4.3316216,4.3316216 0 0 0 4.33224,-4.33224 4.3316216,4.3316216 0 0 0 -4.33224,-4.3312 z m 0.0466,2.45223 a 1.8798386,1.8798386 0 0 1 1.88004,1.87897 1.8798386,1.8798386 0 0 1 -1.88004,1.88003 1.8798386,1.8798386 0 0 1 -1.88004,-1.88003 1.8798386,1.8798386 0 0 1 1.88004,-1.87897 z" />
    <path
       id="path11067-2"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -229.19887,174.77215 a 4.3316216,4.3316216 0 0 0 -4.33119,4.3312 4.3316216,4.3316216 0 0 0 4.33119,4.33224 4.3316216,4.3316216 0 0 0 4.33227,-4.33224 4.3316216,4.3316216 0 0 0 -4.33227,-4.3312 z m 0.0466,2.45223 a 1.8798386,1.8798386 0 0 1 1.88003,1.87897 1.8798386,1.8798386 0 0 1 -1.88003,1.88003 1.8798386,1.8798386 0 0 1 -1.88004,-1.88003 1.8798386,1.8798386 0 0 1 1.88004,-1.87897 z" />
    <path
       id="path11067-8"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -148.15131,175.99225 a 4.3316216,4.3316216 0 0 0 -4.33118,4.3312 4.3316216,4.3316216 0 0 0 4.33118,4.33225 4.3316216,4.3316216 0 0 0 4.33226,-4.33225 4.3316216,4.3316216 0 0 0 -4.33226,-4.3312 z m 0.0466,2.45221 a 1.8798386,1.8798386 0 0 1 1.88005,1.87899 1.8798386,1.8798386 0 0 1 -1.88005,1.88002 1.8798386,1.8798386 0 0 1 -1.88002,-1.88002 1.8798386,1.8798386 0 0 1 1.88002,-1.87899 z" />
    <path
       id="path11067-7"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -260.02255,148.46512 a 4.3316216,4.3316216 0 0 0 -4.3312,4.33119 4.3316216,4.3316216 0 0 0 4.3312,4.33225 4.3316216,4.3316216 0 0 0 4.33226,-4.33225 4.3316216,4.3316216 0 0 0 -4.33226,-4.33119 z m 0.0466,2.45223 a 1.8798386,1.8798386 0 0 1 1.88004,1.87896 1.8798386,1.8798386 0 0 1 -1.88004,1.88004 1.8798386,1.8798386 0 0 1 -1.88004,-1.88004 1.8798386,1.8798386 0 0 1 1.88004,-1.87896 z" />
    <path
       id="path11067-7-9"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -221.85853,137.22081 a 4.3316216,4.3316216 0 0 0 -4.3312,4.33117 4.3316216,4.3316216 0 0 0 4.3312,4.33227 4.3316216,4.3316216 0 0 0 4.33226,-4.33227 4.3316216,4.3316216 0 0 0 -4.33226,-4.33117 z m 0.0466,2.45221 a 1.8798386,1.8798386 0 0 1 1.88004,1.87896 1.8798386,1.8798386 0 0 1 -1.88004,1.88004 1.8798386,1.8798386 0 0 1 -1.88004,-1.88004 1.8798386,1.8798386 0 0 1 1.88004,-1.87896 z" />
    <path
       id="path11067-7-9-3"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -143.63441,100.91566 a 4.3316216,4.3316216 0 0 0 -4.3312,4.3312 4.3316216,4.3316216 0 0 0 4.3312,4.33224 4.3316216,4.3316216 0 0 0 4.33226,-4.33224 4.3316216,4.3316216 0 0 0 -4.33226,-4.3312 z m 0.0466,2.45223 a 1.8798386,1.8798386 0 0 1 1.88003,1.87897 1.8798386,1.8798386 0 0 1 -1.88003,1.88003 1.8798386,1.8798386 0 0 1 -1.88004,-1.88003 1.8798386,1.8798386 0 0 1 1.88004,-1.87897 z" />
    <path
       id="path11067-7-9-3-8"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -59.106125,137.22097 a 4.3316216,4.3316216 0 0 0 -4.331198,4.3312 4.3316216,4.3316216 0 0 0 4.331198,4.33224 4.3316216,4.3316216 0 0 0 4.332265,-4.33224 4.3316216,4.3316216 0 0 0 -4.332265,-4.3312 z m 0.04663,2.45221 a 1.8798386,1.8798386 0 0 1 1.880036,1.87899 1.8798386,1.8798386 0 0 1 -1.880036,1.88004 1.8798386,1.8798386 0 0 1 -1.880036,-1.88004 1.8798386,1.8798386 0 0 1 1.880036,-1.87899 z" />
    <path
       id="path11067-7-9-3-8-8"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -97.229555,191.9726 a 4.3316216,4.3316216 0 0 0 -4.331195,4.3312 4.3316216,4.3316216 0 0 0 4.331195,4.33224 4.3316216,4.3316216 0 0 0 4.332265,-4.33224 4.3316216,4.3316216 0 0 0 -4.332265,-4.3312 z m 0.04663,2.45221 a 1.8798386,1.8798386 0 0 1 1.880036,1.87899 1.8798386,1.8798386 0 0 1 -1.880036,1.88001 1.8798386,1.8798386 0 0 1 -1.880037,-1.88001 1.8798386,1.8798386 0 0 1 1.880037,-1.87899 z" />
    <path
       id="path11067-7-9-3-0"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -122.5929,87.575783 a 4.3316216,4.3316216 0 0 0 -4.33119,4.331197 4.3316216,4.3316216 0 0 0 4.33119,4.332245 4.3316216,4.3316216 0 0 0 4.33227,-4.332245 4.3316216,4.3316216 0 0 0 -4.33227,-4.331197 z m 0.0466,2.452229 a 1.8798386,1.8798386 0 0 1 1.88004,1.878968 1.8798386,1.8798386 0 0 1 -1.88004,1.880036 1.8798386,1.8798386 0 0 1 -1.88003,-1.880036 1.8798386,1.8798386 0 0 1 1.88003,-1.878968 z" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153"
       cx="-228.04485"
       cy="158.44124"
       rx="1.9262234"
       ry="1.8354299" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-8"
       cx="-125.50243"
       cy="191.34106"
       rx="1.9262234"
       ry="1.8354299" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6"
       cx="-203.42741"
       cy="152.79633"
       rx="1.9262234"
       ry="1.8354299" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-2"
       cx="-212.29158"
       cy="135.93016"
       rx="1.9262234"
       ry="1.8354299" />
    <path
       d="m -185.83811,128.97261 h -27.19186 -27.19422 v 2.67263 h 27.19422 27.19186 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.378472"
       id="rect1210-09-7-1-5-4-9-3-0-65" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-0"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -181.32219,123.35017 -3.94268,3.94265 -3.94266,3.94266 0.40977,0.40976 h 2.95965 l 2.46283,-2.46176 3.94267,-3.94265 z" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-7"
       cx="-206.32512"
       cy="124.68648"
       rx="1.9262234"
       ry="1.8354299" />
    <path
       d="m -176.60204,176.758 h -16.3831 -16.38453 v 2.67263 h 16.38453 16.3831 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.293773"
       id="rect1210-09-7-1-5-4-9-3-0-3" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4"
       cx="-209.1218"
       cy="178.09428"
       rx="1.9262234"
       ry="1.8354299" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-5"
       cx="-199.89111"
       cy="191.23561"
       rx="1.9262234"
       ry="1.8354299" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-2"
       cx="-158.49089"
       cy="141.55142"
       rx="1.9262234"
       ry="1.8354299" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-2"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -175.02941,125.3215 -3.94265,-3.94266 -3.94268,-3.94267 0.40979,-0.40977 h 2.95963 l 2.46285,2.46178 3.94265,3.94266 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-19-6"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.124863"
       d="m -179.54532,117.0264 h -2.95963 -2.95992 v 2.67263 h 2.95992 2.95963 z" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-2-6"
       cx="-185.41777"
       cy="118.36272"
       rx="1.9262234"
       ry="1.8354299" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-2-6-2"
       cx="-188.03531"
       cy="104.87174"
       rx="1.9262234"
       ry="1.8354299" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-2-6-2-48"
       cx="-146.33255"
       cy="96.707619"
       rx="1.9262234"
       ry="1.8354299" />
    <path
       d="m -176.60204,171.13554 h -22.38798 -22.38991 v 2.67263 h 22.38991 22.38798 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.343416"
       id="rect1210-09-7-1-5-4-9-3-0-3-3" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-8"
       cx="-222.2191"
       cy="172.47185"
       rx="1.9262234"
       ry="1.8354299" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.307204"
       d="m -105.56124,83.219524 h 17.915023 17.916559 v 2.67263 h -17.916559 -17.915023 z" />
    <path
       d="m -44.509946,145.83754 h 32.784352 32.78723 v 2.67263 h -32.78723 -32.784352 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.415576"
       id="rect1210-09-7-1-5-4-9-2-2" />
    <path
       d="m -48.279714,118.64308 h 22.388 22.3899098 v 2.67265 h -22.3899098 -22.388 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.343416"
       id="rect1210-09-7-1-5-4-9-3-0-6-3" />
    <path
       id="rect1210-09-7-1-5-4-9-3-0-5-3"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.352242"
       d="m -40.020674,177.87184 c 0,0 3.005482,3.00546 3.942676,3.94265 0.853112,0.85312 1.680444,1.68047 1.680444,1.68047 h 22.445786 23.555124 v 2.67196 h -23.555124 -20.593338 -2.959652 l -2.462829,-2.46177 -3.942675,-3.94268 z"
       sodipodi:nodetypes="cscccccccccc" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-8"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -69.50104,145.83754 3.942676,3.94267 3.942655,3.94266 -0.409763,0.40976 h -2.959652 l -2.462829,-2.46176 -3.942675,-3.94268 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-8-4"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -96.69779,184.2769 3.942675,3.94265 3.942655,3.94268 -0.409763,0.40976 h -2.959651 l -2.462829,-2.46178 -3.942676,-3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-45-4"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -41.416706,129.76307 0.340781,0.34076 3.942655,3.94266 -0.409763,0.40976 h -2.959652 l -2.462849,-2.46178 -1.48299,-1.48301 h 3.031818 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-5"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -49.025862,154.13263 3.942655,-3.94266 3.942675,-3.94267 -0.409783,-0.40976 h -2.959631 l -2.46285,2.46178 -3.942654,3.94265 z" />
    <path
       d="m -35.711806,113.02064 h 12.031521 12.032589 v 2.67263 h -12.032589 -12.031521 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.251755"
       id="rect1210-09-7-1-5-4-9-2-2-3" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-5-3"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -40.227722,121.31573 3.942675,-3.94267 3.942675,-3.94266 -0.409783,-0.40976 h -2.959631 l -2.462849,2.46178 -3.942676,3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-5-7"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -72.590109,192.57199 3.942655,-3.94268 3.942675,-3.94265 -0.409783,-0.40976 h -2.959631 l -2.46285,2.46178 -3.942655,3.94265 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-5-7-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -66.184625,186.16755 3.942654,-3.94266 3.942676,-3.94267 -0.409784,-0.40977 h -2.95963 l -2.46285,2.46179 -3.942634,3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-07"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -101.78101,83.219524 -2.21761,2.217612 -3.94264,3.942655 -1.88959,-1.890657 3.94266,-3.942654 0.32695,-0.326956 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-1-8"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.241059"
       d="m -104.45333,76.265574 h 11.030972 11.031939 v 2.67263 h -11.031939 -11.030972 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11-9"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.210679"
       d="m -92.181874,90.1805 h 8.425538 8.426278 v 2.67263 h -8.426278 -8.425538 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11-9-0"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.384668"
       d="m -80.938182,101.77574 h 28.088566 28.091051 v 2.67263 h -28.091051 -28.088566 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11-9-0-6"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.250944"
       d="m -70.257191,107.39818 h 11.953993 11.955 v 2.67263 h -11.955 -11.953993 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11-9-0-6-5"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.176586"
       d="m -64.634729,113.02064 h 5.919303 5.919816 v 2.67263 h -5.919816 -5.919303 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-11-9-0-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.210679"
       d="m -65.297535,96.153275 h 8.425538 8.426278 v 2.67263 h -8.426278 -8.425538 z" />
    <path
       id="rect1210-09-7-1-7-7-5-1-0-1-8-3"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.241059"
       d="m -104.19316,83.219524 h 11.030973 11.03194 v 2.67263 h -11.03194 -11.030973 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-4-4-0-2-4"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171494"
       d="m -101.78124,76.265574 -2.21761,2.220673 -3.94265,3.948099 -1.88957,-1.893266 3.94263,-3.948099 0.32696,-0.327407 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-9-4"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m 24.059436,141.73206 v 2.63056 h 1.14968 l -0.20481,0.20481 -3.94267,3.94265 -1.88959,-1.89065 3.94268,-3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-6-0"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -37.543033,126.16117 0.409763,0.40975 -3.942655,3.94267 -0.944795,0.9448 v -0.94693 h -2.833335 l 1.888521,-1.88852 2.462849,-2.46177 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-1-5"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -52.79561,113.02064 3.942655,3.94266 3.942675,3.94265 -0.409762,0.40976 h -2.959672 l -2.46283,-2.46176 -3.942654,-3.94265 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8-8"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -63.810458,123.3497 3.942655,3.94267 3.942675,3.94266 -0.409763,0.40976 h -2.959651 l -2.462829,-2.46178 -3.942675,-3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8-8-3"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -74.773107,101.77574 3.942654,3.94265 3.942676,3.94266 -0.409763,0.40976 h -2.959651 l -2.46283,-2.46176 -3.942675,-3.94268 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8-8-3-6"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -69.150645,107.39818 3.942655,3.94267 3.942675,3.94266 -0.409763,0.40976 h -2.959651 l -2.462829,-2.46178 -3.942675,-3.94265 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-8-8-8"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -63.810294,134.5936 -1.889588,1.89065 3.805718,3.80572 a 3.0630243,3.0630243 0 0 1 2.066871,-1.71337 l -0.04026,-0.0403 z m 7.680439,7.68044 a 3.0630243,3.0630243 0 0 1 -0.220812,0.61465 h 0.01643 l 0.409783,-0.40976 z" />
    <path
       d="m -59.295261,128.972 h 8.637004 8.637763 v 2.67265 h -8.637763 -8.637004 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.213304"
       id="rect1210-09-7-1-5-4-9-3-0-2-2-1" />
    <path
       id="rect1210-09-7-1-5-4-9-3-0-2-2-1-0"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.459827"
       d="m -56.345758,140.21567 a 14.234503,3.0630243 0 0 1 1.425737,1.3365 14.234503,3.0630243 0 0 1 -1.430646,1.33652 h 26.457402 40.142391 v -2.67302 h -40.142391 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-7-8"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.353322"
       d="m -64.985124,151.46 h 23.698103 23.700137 v 2.67263 h -23.700137 -23.698103 z" />
    <path
       id="rect1210-09-7-1-0-2-2-0-7-8-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.29901"
       d="m -2.3249812,157.08123 h 16.9722972 16.97376 v 2.67263 H 14.647316 -2.3249812 Z" />
    <path
       id="path11628-5"
       style="fill:#250539;fill-opacity:1;stroke-width:0.546533"
       d="m -18.887045,96.154528 a 6.9581615,6.9581615 0 0 1 6.957505,6.957522 6.9581615,6.9581615 0 0 1 -6.957505,6.95855 6.9581615,6.9581615 0 0 1 -6.958572,-6.95855 6.9581615,6.9581615 0 0 1 6.958572,-6.957522 z m 0,2.671973 a 4.2855314,4.2855314 0 0 0 -4.285551,4.285549 4.2855314,4.2855314 0 0 0 4.285551,4.28553 4.2855314,4.2855314 0 0 0 4.284484,-4.28553 4.2855314,4.2855314 0 0 0 -4.284484,-4.285549 z" />
    <path
       id="path11628-7-9"
       style="fill:#250539;fill-opacity:1;stroke-width:0.546533"
       d="m -64.009865,77.598006 a 6.9581615,6.9581615 0 0 1 6.957484,6.957505 6.9581615,6.9581615 0 0 1 -6.957484,6.958572 6.9581615,6.9581615 0 0 1 -6.958593,-6.958572 6.9581615,6.9581615 0 0 1 6.958593,-6.957505 z m 0,2.671953 a 4.2855314,4.2855314 0 0 0 -4.285572,4.285552 4.2855314,4.2855314 0 0 0 4.285572,4.285551 4.2855314,4.2855314 0 0 0 4.284463,-4.285551 4.2855314,4.2855314 0 0 0 -4.284463,-4.285552 z" />
    <path
       id="path11067-2-0"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m 25.986106,137.40066 a 4.3316216,4.3316216 0 0 1 4.33119,4.3312 4.3316216,4.3316216 0 0 1 -4.33119,4.33226 4.3316216,4.3316216 0 0 1 -4.33227,-4.33226 4.3316216,4.3316216 0 0 1 4.33227,-4.3312 z m -0.0466,2.45223 a 1.8798386,1.8798386 0 0 0 -1.88003,1.87897 1.8798386,1.8798386 0 0 0 1.88003,1.88003 1.8798386,1.8798386 0 0 0 1.88004,-1.88003 1.8798386,1.8798386 0 0 0 -1.88004,-1.87897 z" />
    <path
       id="path11067-8-2"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m 45.862796,128.78773 a 4.3316216,4.3316216 0 0 1 4.33117,4.3312 4.3316216,4.3316216 0 0 1 -4.33117,4.33224 4.3316216,4.3316216 0 0 1 -4.33227,-4.33224 4.3316216,4.3316216 0 0 1 4.33227,-4.3312 z m -0.0466,2.45221 a 1.8798386,1.8798386 0 0 0 -1.88005,1.87899 1.8798386,1.8798386 0 0 0 1.88005,1.88001 1.8798386,1.8798386 0 0 0 1.88002,-1.88001 1.8798386,1.8798386 0 0 0 -1.88002,-1.87899 z" />
    <path
       id="path11067-7-3"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -0.48802317,115.64822 a 4.3316216,4.3316216 0 0 1 4.33119697,4.33118 4.3316216,4.3316216 0 0 1 -4.33119697,4.33226 4.3316216,4.3316216 0 0 1 -4.33224503,-4.33226 4.3316216,4.3316216 0 0 1 4.33224503,-4.33118 z m -0.04663,2.45221 a 1.8798386,1.8798386 0 0 0 -1.88003603,1.87897 1.8798386,1.8798386 0 0 0 1.88003603,1.88003 1.8798386,1.8798386 0 0 0 1.88003697,-1.88003 1.8798386,1.8798386 0 0 0 -1.88003697,-1.87897 z" />
    <path
       id="path11067-7-9-3-7"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -40.923725,84.319579 a 4.3316216,4.3316216 0 0 1 4.331176,4.331197 4.3316216,4.3316216 0 0 1 -4.331176,4.332245 4.3316216,4.3316216 0 0 1 -4.332286,-4.332245 4.3316216,4.3316216 0 0 1 4.332286,-4.331197 z m -0.04663,2.452209 a 1.8798386,1.8798386 0 0 0 -1.880036,1.878988 1.8798386,1.8798386 0 0 0 1.880036,1.880036 1.8798386,1.8798386 0 0 0 1.880036,-1.880036 1.8798386,1.8798386 0 0 0 -1.880036,-1.878988 z" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-5"
       cx="-11.187778"
       cy="184.8533"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-5-9"
       cx="-7.9765878"
       cy="172.47244"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-5-2"
       cx="43.693939"
       cy="191.23561"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-5-2-765"
       cx="11.275342"
       cy="166.85001"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <path
       d="M -40.502438,131.78323 H 1.5992668 43.704626 v 2.67263 H 1.5992668 -40.502438 Z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.470939"
       id="rect1210-09-7-1-5-4-9-3-0-3-7" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-5-6"
       cx="-10.249164"
       cy="141.55217"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-2-6-5-2"
       cx="46.112564"
       cy="108.7345"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-2-6-2-4"
       cx="82.390419"
       cy="77.601891"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-2-6-2-4-99"
       cx="75.330055"
       cy="91.516808"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-2-6-2-4-9"
       cx="114.11715"
       cy="89.060921"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <path
       d="m -40.502459,126.16078 h 22.38798 22.3899308 v 2.67264 h -22.3899308 -22.38798 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.343416"
       id="rect1210-09-7-1-5-4-9-3-0-3-3-7" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-45-4-8"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -56.96754,169.11598 0.34078,0.34076 3.942655,3.94267 -0.409763,0.40976 h -2.959651 l -2.46285,-2.46178 -1.482989,-1.48301 h 3.031818 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-7-4-6-0-1"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -53.093868,165.51408 0.409763,0.40976 -3.942655,3.94268 -0.944794,0.94479 v -0.94693 h -2.833335 l 1.88852,-1.88852 2.46285,-2.46178 z" />
    <path
       d="m -56.053272,171.13615 h 32.013533 32.0162848 v 2.67263 H -24.039739 -56.053272 Z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.410658"
       id="rect1210-09-7-1-5-4-9-3-0-3-7-7" />
    <path
       d="m -56.053293,165.51369 h 22.387979 22.389931 v 2.67265 h -22.389931 -22.387979 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.343416"
       id="rect1210-09-7-1-5-4-9-3-0-3-3-7-4" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-8-6"
       cx="-5.1146798"
       cy="127.4971"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-8-6-8"
       cx="90.387894"
       cy="102.57261"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-8-6-8-4"
       cx="85.455322"
       cy="97.603912"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-8-6-6"
       cx="11.647664"
       cy="114.35695"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-6-4-8-6-6-4"
       cx="52.77486"
       cy="124.68648"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-8-7"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -85.454098,184.2769 3.942676,3.94265 3.942655,3.94268 -0.409763,0.40976 h -2.959652 l -2.462829,-2.46178 -3.942675,-3.94266 z" />
    <path
       id="rect1210-09-7-1-5-4-9-6-5-74-8-7-7"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.171376"
       d="m -90.292286,190.68133 3.942676,3.94266 3.942655,3.94265 -0.409763,0.40977 h -2.959652 l -2.462829,-2.46177 -3.942675,-3.94265 z" />
    <path
       d="m -61.668709,177.87245 h 5.274286 5.274738 v 2.67263 h -5.274738 -5.274286 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.166685"
       id="rect1210-09-7-1-5-4-9-3-0-2-01" />
    <path
       d="m -46.041149,177.87184 h 32.675415 32.67825 v 2.67263 h -32.67825 -32.675415 z"
       style="fill:#250539;fill-opacity:1;stroke:none;stroke-width:0.414884"
       id="rect1210-09-7-1-5-4-9-3-0-2-0-5" />
    <path
       id="path11628-7-8-1"
       style="fill:#250539;fill-opacity:1;stroke-width:0.546533"
       d="m 23.598066,172.25063 a 6.9581615,6.9581615 0 0 1 6.95751,6.95752 6.9581615,6.9581615 0 0 1 -6.95751,6.95858 6.9581615,6.9581615 0 0 1 -6.95857,-6.95858 6.9581615,6.9581615 0 0 1 6.95857,-6.95752 z m 0,2.67197 a 4.2855314,4.2855314 0 0 0 -4.28555,4.28555 4.2855314,4.2855314 0 0 0 4.28555,4.28553 4.2855314,4.2855314 0 0 0 4.28448,-4.28553 4.2855314,4.2855314 0 0 0 -4.28448,-4.28555 z" />
    <path
       id="path11628-7-8-1-7"
       style="fill:#250539;fill-opacity:1;stroke-width:0.546533"
       d="m 37.492596,151.46004 a 6.9581615,6.9581615 0 0 1 6.9575,6.9575 6.9581615,6.9581615 0 0 1 -6.9575,6.95858 6.9581615,6.9581615 0 0 1 -6.95857,-6.95858 6.9581615,6.9581615 0 0 1 6.95857,-6.9575 z m 0,2.67195 a 4.2855314,4.2855314 0 0 0 -4.28555,4.28555 4.2855314,4.2855314 0 0 0 4.28555,4.28556 4.2855314,4.2855314 0 0 0 4.28448,-4.28556 4.2855314,4.2855314 0 0 0 -4.28448,-4.28555 z" />
    <path
       id="path11067-3"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -48.82514,174.87747 a 4.3316216,4.3316216 0 0 1 4.331197,4.3312 4.3316216,4.3316216 0 0 1 -4.331197,4.33226 4.3316216,4.3316216 0 0 1 -4.332266,-4.33226 4.3316216,4.3316216 0 0 1 4.332266,-4.3312 z m -0.04663,2.45223 a 1.8798386,1.8798386 0 0 0 -1.880036,1.87897 1.8798386,1.8798386 0 0 0 1.880036,1.88003 1.8798386,1.8798386 0 0 0 1.880036,-1.88003 1.8798386,1.8798386 0 0 0 -1.880036,-1.87897 z" />
    <path
       id="path11067-3-8"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -28.804693,193.30871 a 4.3316216,4.3316216 0 0 1 4.331197,4.3312 4.3316216,4.3316216 0 0 1 -4.331197,4.33224 4.3316216,4.3316216 0 0 1 -4.332265,-4.33224 4.3316216,4.3316216 0 0 1 4.332265,-4.3312 z m -0.04663,2.45223 a 1.8798386,1.8798386 0 0 0 -1.880036,1.87897 1.8798386,1.8798386 0 0 0 1.880036,1.88003 1.8798386,1.8798386 0 0 0 1.880036,-1.88003 1.8798386,1.8798386 0 0 0 -1.880036,-1.87897 z" />
    <path
       id="path11067-3-8-1"
       style="fill:#250539;fill-opacity:1;stroke-width:0.537735"
       d="m -5.2454372,154.08635 a 4.3316216,4.3316216 0 0 1 4.33119703,4.33119 4.3316216,4.3316216 0 0 1 -4.33119703,4.33227 4.3316216,4.3316216 0 0 1 -4.332266,-4.33227 4.3316216,4.3316216 0 0 1 4.332266,-4.33119 z m -0.04663,2.45223 a 1.8798386,1.8798386 0 0 0 -1.880036,1.87896 1.8798386,1.8798386 0 0 0 1.880036,1.88004 1.8798386,1.8798386 0 0 0 1.880036,-1.88004 1.8798386,1.8798386 0 0 0 -1.880036,-1.87896 z" />
    <ellipse
       style="fill:#250539;fill-opacity:1;stroke-width:0.570983"
       id="path12153-5-2-76"
       cx="17.586851"
       cy="152.79633"
       rx="1.9262234"
       ry="1.8354299"
       transform="scale(-1,1)" />
  </g>
</svg>

embark/static/content/images/bg.png METASEP

embark/static/content/css/serviceDashboard.css METASEP
.tile {
    width: 100%;
    display: inline-block;
    box-sizing: border-box;
    background: #fff;
    padding: 10px;
    margin-bottom: 10px;
}

.containerCSS {
    width: 100%;
    padding: 15px;
}

.title{
    text-align: center;
}



h3 {
    font-weight: 300;
    line-height: 1.3em;
}


.coldiv {
    width: 100%;
}

.containerCSS div {
    width: inherit;
}

.statusTile {
    background-color: lavender;
}


.statusEMba {
    display: flex;
}

.headerText {
    margin: 5px;
}

.log_phase {
    font-size: small;
    margin-bottom: 5px;
    overflow-y: scroll;
}

.progress {
    height: 20px;
    margin-top: 10px;
    background-color: #493791;
    padding: 0%;
}

.progress-bar {
    width: 0%;

}

.log {
    width: 100%;
    margin: 3px;

}

.sub-container {
    height: 100vh;
}

.logUL {
    height: 100px;
    max-height: 300px;
    overflow-y: auto;
    --scrollbarBG: #d7e4ee;
    --thumbBG: #b8cedb;
}

/* Styles for the dataform fields */

.form-check-input:checked {
    background-color: #493791;
    border-color: #493791;
}

.formTxtField,
.dropdownSelect {
    width: 80%;
    height: 2rem;
    border: none;
    border-radius: 0;
    padding: 6px 12px 6px 12px;
    margin-top: 15px;
    background-color: #acacac;
}

.form-control:focus {
    background-color: #acacac;
}

.dataRow {
    border-left-style: solid;
    border-color: #b8cedb;
    height: 90vh;
    overflow: scroll;
    overflow-x: hidden;
    padding: 20px;
    padding-top: 0px;
    padding-right: 5px;
    font-size: small;
}

/*Uploaded Files*/
.statusTile {
    background-color: #009999;
}

.iconLabel {
    display: block;
    text-align: center;
}

.stopBtnRow {
    justify-content: center;
}


.fileName {
    display: block;
    align-items: center;
}

.stopAnalysis {
    display: flex;
    align-items: baseline;
}

.stopAnalysis>select {
    margin-left: 10px;
}

#file-input {
    visibility: hidden;
    width: 0;
}

.form-check-input[type="checkbox"] {
    border-radius: 0;
}

#analysisToStop {
    margin-bottom: 10px;
}

/* Analysis */

.form-group>.form-check-input {
    left: 22px;
}

.expertModeOptions[value="expmode_off"] {
    display: none;
}
.expertModeOptions[value="expmode_on"] {
    display: block;
}


.form-group>label {
    padding-left: 30px;
}

.text-muted[value="help_text_off"] {
    display:none;
}

.text-muted[value="help_text_on"] {
    display:block;
}

.alert {
    margin: 30px;
}
/* ::-webkit-scrollbar {
    width: 11px;
}

::-webkit-scrollbar-track {
    background: var(--scrollbarBG);
}

::-webkit-scrollbar-thumb {
    background-color: var(--thumbBG);
    border-radius: 6px;
    border: 3px solid var(--scrollbarBG);
} */

embark/static/content/css/reportDashboard.css METASEP
.tableData {
    margin-top: 15px;
}

/* Search bar styles */
.searchBar {
    padding: 3px;
    border: solid 1px #E4E4E4;
    background-color: #fff;
}

.searchBar input {
    border: 0;
    box-shadow: none;
}

.searchBar button {
    margin: 2px 0 0 0;
    background: none;
    box-shadow: none;
    border: 0;
    color: #666666;
    padding: 6 8px 6 8px;
}

.searchBar button:hover {
    border: 0;
    box-shadow: none;
}

.form-control {
    transition: none;
    border: none;
}

.page-item:first-child .page-link {
    border-radius: 0;
}

.page-item:last-child .page-link {
    border-radius: 0;
}

.page-item.active .page-link {
    z-index: 3;
    color: #fff;
    background-color: #493791;
    border-color: #493791;
}

table.dataTable td, table.dataTable th {
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

.buttonRowElem {
    margin: 2px;
}
embark/static/content/css/passwordReset.css METASEP
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: sans-serif;
}

html, body {
    height: 100%;
}

a {
    color: #fff;
    text-decoration: none;
}

form {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 0 5rem;
    grid-column: 1 / 2;
    grid-row: 1 / 2;
}

.input-field {
    max-width: 380px;
    width: 100%;
    height: 55px;
    background-color: #f0f0f0;
    margin: 10px 0;
    display: grid;
    grid-template-columns: 15% 85%;
    padding: 0 0.4rem;
    position: relative;
}

.box-body-second{
    position: center;
    text-align: center;
    color:#FF0000;
}

.col-md-3 {
    flex: 0 0 auto;
    width: 100%;
}

.h2, h2 {
    font-size: 2rem;
    color: rgb(0, 0, 0);
    margin-bottom: 10px;
    font-weight: bold;
    text-align: center;
}

.btn-reset {
    width: 220px;
    height: 49px;
    outline: black;
    border: black;
    background-color: #4d4197;
    color: #ffffff;
    text-transform: uppercase;
    font-weight: 600;
    transition: all 0.5s;
}

.btn-cnt{
    text-align: center;
    display: flex;
    justify-content: center;
    align-items: center;
}

h2.page-header{
    padding:30px;
}
embark/static/content/css/navigation.css METASEP
/* navigation menu */
#navigation {
    position: fixed;
    width: 80px;
    height: 100%;
    background: #29043f;
    top: 0;
    left: 0;
    padding: 6px;
}

#navigation.active {
    width: 300px;
}

#navigation .logo-content {
    height: 60px;
    margin-bottom: 20px;
}

#navigation .logo-content .logo {
    display: flex;
    text-decoration: none;
    color: #eee;
    height: 30px;
    align-items: center;
    left: 0;
    opacity: 0;
    pointer-events: none;
}

#navigation.active .logo-content .logo {
    opacity: 1;
    pointer-events: none;
}

#navigation ul {
    padding-left: 0;
}

.logo-content .logo img {
    min-width: 50px;
    margin-right: 5px;
    margin-top: 40px;
    object-fit: cover;
    margin-left: 10px;
}

.logo-content .logo a {
    display: flex;
    text-decoration: none;
}

#navigation #menuBtn {
    position: absolute;
    color: #EEEEEE;
    font-size: 24px;
    left: 50%;
    top: 10px;
    height: 60px;
    width: 60px;
    text-align: center;
    line-height: 40px;
    transform: translateX(-50%);
    cursor: pointer;
}

#navigation #menuBtn:hover {
    color: #ffcc00;
}

#navigation.active #menuBtn {
    left: 86%;
}

#navigation ul li {
    line-height: 40px;
    font-size: 20px;
    flex-wrap: wrap;
    justify-content: space-between;
    margin: 45px 0 0 0;
}

#navigation ul li a {
    display: flex;
    text-decoration: none;
    color: #eee;
    margin-left: 10px;
}

#navigation .nav-link {
    margin-left: 10px;
    opacity: 0;
    pointer-events: none;
    color: #fff;
    width: 100%;
    height: min-content;
}

#navigation .nav-link:hover {
    color: #ffcc00;
}

#navigation.active .nav-link {
    opacity: 1;
    pointer-events: auto;
}

#navigation ul li a:hover {
    color: #ffcc00;
}

/* profile */
#navigation .profile-content {
    position: absolute;
    color: #eee;
    bottom: 0;
    left: 0;
    width: 100%;
    background: #30294d;
    padding-bottom: 7px;
}

#navigation .profile-content .profile {
    position: relative;
    padding: 10px 6px;
    height: 65px;
}

#navigation.active .profile .profile-details {
    opacity: 1;
    pointer-events: auto;
}

.icon {
    margin-top: 7px;
}

.profile-content .profile .profile-details {
    display: flex;
    align-items: center;
    margin-left: 10px;
    opacity: 0;
    pointer-events: none;
}


.profile .profile-details .user-id {
    font-size: 14px;
    margin-left: 5px;
}

.profile .profile-details {
    height: 45px;
    width: 45px;
    object-fit: cover;
    line-height: 45px;
    font-size: 30px;
}

.profile #log-out {
    position: absolute;
    left: 50%;
    bottom: 5px;
    transform: translateX(-50%);
    min-width: 50px;
    line-height: 45px;
    font-size: 20px;
    text-align: center;
    color: #eee;
}

.profile #log-out:hover {
    color: #ffcc00;
}

#navigation.active .profile #log-out {
    left: 86%;
}

.main {
    position: absolute;
    min-height: 100vh;
    left: 80px;
    width: calc(100% - 80px);
    /*transition: all 0.5s ease;*/
    padding: 15px;
}

#navigation.active~.main {
    width: calc(100% - 300px);
    left: 300px;
}

.dropdown {
  display: inline-block;
  position: relative;
}

.dropdown-content {
    display: none;
    position: absolute;
    width: 150%;
    overflow: initial;
    font-size: 15px;
    line-height: 0.3em;
    margin-left: 70px;
}

.dropdown:hover .dropdown-content {
  display: block;
}
.dropdown-content a {
  display: block;
  color: #000000;
  padding-top: 5px;
  text-decoration: none;
}
.dropdown-content a:hover {
  color: #eee;
}

embark/static/content/css/menu.css METASEP

embark/static/content/css/mainDashboard.css METASEP
.cardBox {
    position: relative;
    width: 100%;
    padding: 10px 10px 0 10px;
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    grid-gap: 10px;
    height: 125px !important;
}

.card-body {
    background: #e0e0e0;
    color: #2a225c;
    text-align: center;
}

.cardBox>.card {
    padding: 0;
}

.card-title,
.cardDataName {
    margin: 0px;
    display: block;
    text-align: center;
    height: 30%;
    padding: 5px;
    color: #1e0e28;
}

.card {
    padding: 15px;
    border-radius: 0;
    border-width: 0;
}

.card-wide {
    height: 400px;
    max-height: 400px;
}

.card-left {
    height: 600px;
}

.card-right {
    height: 600px;
    grid-template-rows: 80% auto;
    grid-gap: 10px;
    display: grid;
}

.card-pie {
    height: 350px;
}

.title-row {
    height: 10%;
    justify-content: center;
}

canvas {
    width: auto !important;
    margin: 5px;
}

#entropyMeterLabel {
    text-align: center;
    padding-top: 15px;
    border-top: 1px solid #b8cedb;
}

meter {
    width: 100%;
}

/* each data row styles */

.cardRow {
    margin: 10px;
    display: grid;
    grid-gap: 10px;
}

.dataCardRow {
    grid-template-columns: 60% auto;
}

.dataCardRow2 {
    grid-template-columns: repeat(5, 1fr);
}

.dataCardRow4 {
    grid-template-columns: 50% auto;
}

.dataCardRow5 canvas {
    max-height: 350px !important;
}
embark/static/content/css/login.css METASEP
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: sans-serif;
}

html, body {
    height: 100%;
}

a {
    color: #fff;
    text-decoration: none;
}

.container-fluid {
    overflow: hidden;
}

form {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 0 5rem;
    grid-column: 1 / 2;
    grid-row: 1 / 2;
}

.title {
    font-size: 2.2rem;
    color: rgb(255, 255, 255);
    margin-bottom: 10px;
    font-weight: bold;
}

.login {
    margin-top: 100px;
}

.input-field {
    max-width: 380px;
    width: 100%;
    height: 55px;
    background-color: #f0f0f0;
    margin: 10px 0;
    display: grid;
    grid-template-columns: 15% 85%;
    padding: 0 0.4rem;
    position: relative;
}

.input-field i {
    text-align: center;
    line-height: 55px;
    color: #ffffff;
    font-size: 1.5rem;
}

.input-field input {
    background: none;
    outline: none;
    border: none;
    line-height: 1;
    font-weight: 600;
    font-size: 1.2rem;
    color: #333;
}

.input-field input:placeholder-shown {
    color: #aaa;
    font-weight: 500;
}

#loginButton {
    margin-top: 30px;
}

.btn-login {
    width: 150px;
    height: 49px;
    outline: none;
    border: none;
    background-color: #4d4197;
    color: #ffffff;
    text-transform: uppercase;
    font-weight: 600;
    margin: 10px, 0;
    transition: all 0.5s;
    display: inline-block;
}

.btn-login:hover {
    background-color: #6a5ebe;
}

#embarkLogo {
    width: 100%;
    padding: 50px;
}

#embarkLogo img {
    width: 60%;
    max-width: 600px;
    display: block;
    margin-left: auto;
    margin-right: auto;
}

::selection{  
    background-color: #160022;
    color: #ffcc00;
}  

.alert {
    margin: 30px;
}

#login_footer {
    text-align: center;
    margin: 50px 0;
}
embark/static/content/css/log.css METASEP
#logHeader {
    padding: 1rem;
    background-color: #333;
    color: #fff;
    text-align: center;
}

#logBox {
    color: #fff;
    white-space: pre-wrap;
    font-family: monospace;
    font-size: 14px;
}
embark/static/content/css/individualReportDashboard.css METASEP
.leftCol {
    grid-template-rows: 25% auto;
    display: grid;
    grid-gap: 10px;
    max-height: 100%;
}

.dataCardIRRow1 {
    grid-template-columns: 30% auto;
    grid-gap: 10px;
    display: grid;
    margin-bottom: 10px;
}

.dataCardIRRow2 {
    grid-template-columns: repeat(4, 1fr);
    display: grid;
    grid-gap: 10px;
    height: 350px;
}

.IRReportCard {
    max-height: 350px;
}

.IRReportCard1 {
    max-height: 450px;
}

.table-individualrep tr td:first-child {
  min-width: 350px;
}

embark/static/content/css/globalStyle.css METASEP
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: Helvetica, sans-serif;
    font-weight: 500;
}

body {
    overflow-x: hidden;
    background: #1a1a1a;
    background-image: url('../images/bg.png');
    background-repeat: repeat;
}

.container-fluid,
.row {
    padding: 0;
    margin: 0;
}

.col {
    max-width: 100%;
    padding: 0;
    margin: 0;
}

.main {
    color: #fff;
}

input:focus,
select:focus,
textarea:focus,
.form-control:focus,
.btn:focus {
    outline: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    box-shadow: none;
}

/*Progress bar*/
.progress-bar {
    background-color: #493791;
}

/* button styles */
.btn {
    width: fit-content;
    font-weight: normal;
    border-radius: 0px;
    height: 40px;
    line-height: 1;
    font-size: medium;
    color: #FFFFFF;
    margin: 15px 0 10px 10px;
}

.btn:hover {
    cursor: pointer;
}

/* submit button */
button[type="submit"] {
    background: #493791;
    color: #FFFFFF;
}

button[type="submit"]:hover {
    background: #2d2357;
    color: #FFFFFF;
}

button[type="submit"]:disabled {
    background: #3f3f3f;
    color: #5f5f5f;
}

/* general button */
button[type="button"] {
    background: #7b919d;
    color: #FFFFFF;
}

button[type="button"]:hover {
    background: #525d63;
    color: #FFFFFF;
}

/* reset /cancel / delete buttons */
button[type="reset"],
button[type="stop"],
button[type="delete"] {
    background: #e3141b;
    color: #FFFFFF;
}

button[type="reset"]:hover,
button[type="stop"]:hover,
button[type="delete"]:hover {
    background: #870005;
    color: #FFFFFF;
}

.buttonRow {
    height: 60px;
    padding-right: calc(var(--bs-gutter-x) * .5);
    padding-left: calc(var(--bs-gutter-x) * .5);
    margin-bottom: 10px;
}

.buttonRowElem {
    display: block;
    float: right;
}

.form-select {
    border-radius: 0;
    margin: 0 5px;
    border: none;
}

.form-select:focus {
    box-shadow: none;
    border: none;
}

.progress-wrapper {
    display: none;
    margin-top: 10px;
}

.box {
    margin-bottom: calc(var(--bs-gutter-x));
    background-color: #fff;
    color: #000;
    padding-top: 12px;
}

.innerBlock {
    margin-left: 30px;
}

.mainText {
    font-size: 24px;
    margin-bottom: 12px;
    text-align: center;
    color: #1e0e28ff;
}

.subText {
    font-size: medium;
    text-align: center;
}
embark/static/content/css/fileUpload.css METASEP
.progress-wrapper {
    display: none;
}

.progress-bar {
    width: 0% ;
}

.uploadRow {
    height: 100px;
}

/* Styles for the slider */
/* The switch - the box around the slider */
.switch {
    position: relative;
    display: inline-block;
    width: 40px;
    height: 10px;
    top: 5px;
    left: 10px;
}

/* Hide default HTML checkbox */
.switch input {
    opacity: 0;
    width: 0;
    height: 0;
}

/* The slider */
.slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #a791ff;
    -webkit-transition: .4s;
    transition: .4s;
}

.slider:before {
    position: absolute;
    content: "";
    height: 16px;
    width: 16px;
    bottom: -3px;
    background-color: rgb(48, 48, 48);
    -webkit-transition: .4s;
    transition: .4s;
}

input:checked+.slider {
    background-color: #493791;
}

input:focus+.slider {
    box-shadow: 0 0 1px #493791;
}

input:checked+.slider:before {
    -webkit-transform: translateX(26px);
    -ms-transform: translateX(26px);
    transform: translateX(26px);
}



/* Rounded sliders */
.slider.round {
    border-radius: 34px;
}

.slider.round:before {
    border-radius: 50%;
}

/* Styles for the dataform fields */

.form-check-input:checked {
    background-color: #493791;
    border-color: #493791;
}

.formTxtField,
.dropdownSelect {
    width: 80%;
    height: 2rem;
    border: none;
    border-radius: 0;
    padding: 6px 12px 6px 12px;
    margin-top: 15px;
    background-color: #acacac;
}

.form-control:focus {
    background-color: #acacac;
}

.dataRow {
    border-left-style: solid;
    border-color: #b8cedb;
    height: 90vh;
    overflow: scroll;
    overflow-x: hidden;
    padding: 20px;
    padding-top: 0px;
    padding-right: 5px;
    font-size: small;
}

/* Styles for file upload */

.deleteIconRow {
    border-top-style: solid;
    border-color: #b8cedb;
    padding: 40px;
    justify-content: center;
}

.fileDrop>input {
    display: none;
}

.fileuploadIcon {
    color: #009999
}

.uploadIconRow {
    display: block;
}

/*Uploaded Files*/
.statusTile {
    background-color: #009999;
}

.iconLabel {
    display: block;
    text-align: center;
}

.deleteBtnRow {
    justify-content: center;
}


.fileName {
    display: block;
    align-items: center;
}

.deleteFirmware {
    display: flex;
    align-items: baseline;
}

.deleteFirmware>select {
    margin-left: 10px;
}

#file-input {
    visibility: hidden;
    width: 0;
}

.form-check-input[type="checkbox"] {
    border-radius: 0;
}

#firmwareToDelete {
    margin-bottom: 10px;
}
/* Analysis */

.form-group>.form-check-input {
    left: 22px;
}


.expertModeOptions[value="expmode_off"] {
    display: none;
}
.expertModeOptions[value="expmode_on"] {
    display: block;
}


.form-group>label {
    padding-left: 30px;
}

.text-muted[value="help_text_off"] {
    display:none;
}

.text-muted[value="help_text_on"] {
    display:block;
}

.alert {
    margin: 30px;
}
embark/uploader/management/commands/test_runapscheduler.py METASEP
import time

from django.test import TestCase

from uploader.models import ResourceTimestamp
from .runapscheduler import resource_tracker, delete_old_job_executions


class UnpackerTest(TestCase):

    def setUp(self):
        # nothing to do here
        pass

    def test_resource_entry_store(self):
        init_len = len(ResourceTimestamp.objects.all())

        resource_tracker()

        self.assertEqual(init_len + 1, len(ResourceTimestamp.objects.all()),
                         "resource_tracker() does not store entry properly")

    def test_resource_entry_store_delete(self):

        old_jobs = 5
        new_jobs = 5

        for _ in range(old_jobs):
            resource_tracker()

        # make jobs old jobs
        time.sleep(1)

        for _ in range(new_jobs):
            resource_tracker()

        # delete at least all old jobs
        delete_old_job_executions(max_age=1)

        self.assertLessEqual(len(ResourceTimestamp.objects.all()), new_jobs,
                             "delete_old_job_executions() does not delete properly")

embark/uploader/management/commands/runapscheduler.py METASEP
import datetime as dtime
import logging
import psutil

from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
from django.conf import settings
from django.core.management.base import BaseCommand
from django.utils.datetime_safe import datetime
from django_apscheduler.jobstores import DjangoJobStore
from django_apscheduler.models import DjangoJobExecution
from django_apscheduler import util

from uploader.models import ResourceTimestamp

logger = logging.getLogger("web")

CPU_UPPER_BOUND = 90
MEMORY_UPPER_BOUND = 80


def resource_tracker():
    """
    This job tracks the current cpu and memory usage and stores Timestamp objects
    in the db for further usage.

    """
    cpu_percentage = psutil.cpu_percent()
    memory_percentage = psutil.virtual_memory().percent

    # inform user about high load
    # if cpu_percentage > CPU_UPPER_BOUND:
    #    logger.warning(f"High CPU usage: {cpu_percentage}%")
    # if memory_percentage > MEMORY_UPPER_BOUND:
    #    logger.warning(f"High Memory usage: {memory_percentage}%")

    # save timestamps in db
    res_timestamp = ResourceTimestamp(cpu_percentage=cpu_percentage, memory_percentage=memory_percentage)
    res_timestamp.save()


@util.close_old_connections
def delete_old_job_executions(max_age=1_209_600):
    """
    This job deletes APScheduler job execution entries older than `max_age` from the database,
    as well as deleting all timestamps older than 'max_age'
    It helps to prevent the database from filling up with old historical records that are no
    longer useful.

    :param max_age: The maximum length of time to retain historical job execution records.
                    Defaults to 14 days.
    """
    # delete job execution entries
    DjangoJobExecution.objects.delete_old_job_executions(max_age)

    # delete entries in db older than 2 weeks
    time_delta = datetime.now() - dtime.timedelta(seconds=max_age)
    ResourceTimestamp.objects.all().filter(timestamp__lt=time_delta).delete()


class Command(BaseCommand):     # FIXME change cycle times
    """
    Extends Base command to feature another command called runapscheduler by the manage.py script
    like this "python3 manage.py runapscheduler"
    """

    help = "Runs resource tracker as well as cleanup process."

    def add_arguments(self, parser):
        # Named optional argument
        parser.add_argument(
            '--test',
            action='store_true',
            help='sample every minute for testing purpose',
        )

    def handle(self, *args, **options):
        """
        handler for runapscheduler command
        """
        scheduler = BlockingScheduler(timezone=settings.TIME_ZONE)
        scheduler.add_jobstore(DjangoJobStore(), "default")

        # configure CronTrigger
        if options['test']:
            # Every hour -> changed to 5 seconds
            resource_tracker_trigger = CronTrigger(second="*/5")
            # every 30 minutes
            delete_old_job_executions_tigger = CronTrigger(minute="*/30")
            delete_old_than = 900
        else:
            # Every hour
            resource_tracker_trigger = CronTrigger(minute="00")
            # everyday at midnight
            delete_old_job_executions_tigger = CronTrigger(hour="00", minute="00")
            delete_old_than = 1_209_600

        # start resource_tracker
        scheduler.add_job(
            resource_tracker,
            trigger=resource_tracker_trigger,
            id="resource_tracker",
            max_instances=1,
            replace_existing=True,
        )
        logger.info("Added CronTrigger job %s.", resource_tracker.__name__)
        logger.info("Added CronTrigger resource_tracker_trigger: %s.", resource_tracker_trigger)
        logger.info("Added CronTrigger delete_old_job_executions_trigger: %s.", delete_old_job_executions_tigger)

        # start cleanup jobresource_tracker
        scheduler.add_job(
            delete_old_job_executions,
            trigger=delete_old_job_executions_tigger,
            id="delete_old_job_executions",
            max_instances=1,
            replace_existing=True,
            args=(delete_old_than,)
        )
        logger.info("Added weekly job: 'delete_old_job_executions'.")

        try:
            logger.info("Starting scheduler for tracking...")
            scheduler.start()
        except KeyboardInterrupt:
            logger.info("Stopping scheduler for tracking...")
            scheduler.shutdown()
            logger.info("Scheduler for tracking shut down successfully!")

embark/uploader/management/commands/__init__.py METASEP

embark/templates/user/register.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/login.css' %}"/>{% endblock style %}
{% block title %}EMBArk register{% endblock title %}
{% block maincontent %}
    <div class="login-form-container">
        {% if error_message %}
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
            <medium>{{ message }}</medium>
        </div>
        {% endif %}
        {% if success_message %}
        <div class="alert alert-success alert-dismissible fade show" role="alert">
            <medium>{{ message }}</medium>
        </div>
        {% endif %}
        <div id="embarkLogo"><img src="{% static 'content/images/embark_logo.svg' %}" alt="EMBArk logo graphic" height="auto" width="auto"/></div>
        <div class="login">
            <form action="{% url 'embark-register' %}" class="login-form" method="post" novalidate>
                <h2 class="title">Register</h2>
                <div class="input-field" data-error="Username is required">
                    <svg viewBox="0 -2 25 25" xmlns="http://www.w3.org/2000/svg"><path d="m7.5.5c1.65685425 0 3 1.34314575 3 3v2c0 1.65685425-1.34314575 3-3 3s-3-1.34314575-3-3v-2c0-1.65685425 1.34314575-3 3-3zm7 14v-.7281753c0-3.1864098-3.6862915-5.2718247-7-5.2718247s-7 2.0854149-7 5.2718247v.7281753c0 .5522847.44771525 1 1 1h12c.5522847 0 1-.4477153 1-1z" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" transform="translate(3 2)"/></svg>
                    <input type="text" placeholder="Username" name="username" required/>
                </div>
                <div class="input-field">
                    <svg viewBox="0 -2 25 25" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" transform="translate(4 1)"><path d="m2.5 8.5-.00586729-1.99475098c-.00728549-4.00349935 1.32800361-6.00524902 4.00586729-6.00524902s4.0112203 2.00174967 4.0000699 6.00524902v1.99475098m-8.0000699 0h8.0225317c1.0543618 0 1.9181652.81587779 1.9945143 1.8507377l.0054778.1548972-.0169048 6c-.0031058 1.1023652-.8976224 1.9943651-1.999992 1.9943651h-8.005627c-1.1045695 0-2-.8954305-2-2v-6c0-1.1045695.8954305-2 2-2z" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="6.5" cy="13.5" fill="#000" r="1.5"/></g></svg>
                    <input type="password" placeholder="Password" name="password" required/>
                </div>
                <div class="input-field">
                    <svg viewBox="0 -2 25 25" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" transform="translate(4 1)"><path d="m2.5 8.5-.00586729-1.99475098c-.00728549-4.00349935 1.32800361-6.00524902 4.00586729-6.00524902s4.0112203 2.00174967 4.0000699 6.00524902v1.99475098m-8.0000699 0h8.0225317c1.0543618 0 1.9181652.81587779 1.9945143 1.8507377l.0054778.1548972-.0169048 6c-.0031058 1.1023652-.8976224 1.9943651-1.999992 1.9943651h-8.005627c-1.1045695 0-2-.8954305-2-2v-6c0-1.1045695.8954305-2 2-2z" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="6.5" cy="13.5" fill="#000" r="1.5"/></g></svg>
                    <input type="password" placeholder="Confirm password" name="confirm_password" required/>
                </div>
                <input id="loginButton" type="submit" class="solid btn-login" value="Register" />
            </form> 
        </div>
        <div id="login_footer">
            <a href="{% url 'embark-login' %}">
                <input class="solid btn-login" type="submit" value="Back" />
            </a>
        </div>
    </div>
{% endblock maincontent %}
embark/templates/user/passwordChangeDone.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/passwordReset.css' %}"/>{% endblock style %}
{% block navigation %}{% include "navigation.html" %}{% endblock navigation %}
{% block maincontent %}
    <div class="box box-primary">
    <h2> Password successfully changed! </h2>
        <div class="btn-cnt">
        <a href="{% url 'embark-MainDashboard' %}">
            <input class="btn-reset" type="submit" value="Back to dashboard" />
        </a>
        </div>
    </div>
{% endblock maincontent %}
embark/templates/user/passwordChange.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/passwordReset.css' %}"/>{% endblock style %}
{% block title %} Change password {% endblock title %}
{% block navigation %}{% include "navigation.html" %}{% endblock navigation %}
{% block maincontent %}
    <div class="password-change-container">
    {% if error_message %}
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
            <medium>{{ message }}</medium>
        </div>
    {% endif %}
    <div class="change-password">
<form action="{% url 'embark-password-change' %}" class="password-change-form" method="post" novalidate>
    <div class="box box-primary">
        <div class="box-header">
            <h2 class="page-header"><i class="fa fa-lock"></i> Change Password</h2>
        </div>
        <!-- /.box-header -->
        <div class="box-body">
            <div class="row">
                <div class="col-xs-12 col-sm-3 col-md-3">
                    <label>Old Password</label>
                </div>
                <div class="col-xs-12 col-sm-3 col-md-3">
                    <div class="input-group">
                        <div class="input-group-addon">
                            <i class="fa fa-lock"></i>
                        </div>
                        <input class="input-field" id="oldPassword" name="oldPassword" placeholder="Enter the Old Password" type="password" required/>
                    </div>
                </div>
                <!-- /.input group -->
            </div>
            <br/>
            <div class="row">
                <div class="col-xs-12 col-sm-3 col-md-3">
                    <label>New Password</label>
                </div>
                <div class="col-xs-12 col-sm-3 col-md-3">
                    <div class="input-group">
                        <div class="input-group-addon">
                            <i class="fa fa-lock"></i>
                        </div>
                        <input class="input-field" id="newPassword" name="newPassword" placeholder="Enter the New Password" type="password" required/>
                    </div>
                </div>
                <!-- /.input group -->
            </div>
            <br/>
            <div class="row">
                <div class="col-xs-12 col-sm-3 col-md-3">
                    <label>Confirm Password</label>
                </div>
                <div class="col-xs-12 col-sm-3 col-md-3">
                    <div class="input-group">
                        <div class="input-group-addon">
                            <i class="fa fa-lock"></i>
                        </div>
                        <input class="input-field" id="confirmPassword" name="confirmPassword" placeholder="Re-enter the New Password" type="password" required/>
                    </div>
                </div>
                <!-- /.input group -->
            </div>
        </div>

            <div class="pull-right">
                <button type="submit" name="Submit" value="Save" class="btn btn-danger"><i class="livicon" data-n="pen" data-s="16" data-c="#fff" data-hc="0" ></i> Save</button>
                <button type="reset" name="Reset" value="Clear" class="btn btn-primary"><i class="livicon" data-n="trash" data-s="16" data-c="#fff" data-hc="0"></i> Clear</button>
            </div>
        </div>

    </form>
    </div>
</div>
{% endblock maincontent %}
embark/templates/user/menu.html METASEP
{% extends 'base.html' %}
{% load static %}
{% block title %} EMBArk User-menu {% endblock title %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/menu.css' %}"/>{% endblock style %}
{% block navigation %}{% include "navigation.html" %}{% endblock navigation %}
{% block maincontent %}
<!--TODO make work-->
<!--
    <div class="container-fluid">
      <div class="card card-outline card-primary">
        <div class="card-header">
          <h3 class="card-title">{{ userList.count }} Users</h3>

          <div class="card-tools">
            <button type="button" class="btn btn-tool" data-card-widget="collapse" title="Collapse">
              <i class="fas fa-minus"></i>
            </button>
          </div>
        </div>
        <div class="card-body">
          <table id="userlist" class="table table-bordered table-hover">
              <thead>
                  <tr>
                      <th style="width: 15%">
                          User
                      </th>
                      <th style="width: 20%">
                          Email
                      </th>
                      <th style="width: 5%">
                          Active
                      </th>
                      <th style="width: 10%">
                          Group
                      </th>
                      <th style="width: 5%">
                          Admin
                      </th>
                      <th style="width: 20%" class="text-center">
                          Actions
                      </th>
                  </tr>
              </thead>
              <tbody>

                {% for group in group_list %}

                  {% for user in group.user_set.all %}

                    <tr>
                      <td>
                        {{ user.username }}
                      </td>
                      <td>
                        {{ user.email }}
                      </td>
                      <td class="text-center">
                        {% if user.is_active %}
                            <i class="fas fa-check-circle text-success"></i>
                        {% else %}
                            <i class="fas fa-times-circle text-danger"></i>
                        {% endif %}
                      </td>
                      <td class="text-center">
                        {{ group }}
                      </td>
                      <td class="text-center">
                        {% if user.is_superuser %}
                            <i class="fas fa-check-circle text-success"></i>
                        {% else %}
                            <i class="fas fa-times-circle text-danger"></i>
                        {% endif %}
                      </td>

                      <td class="project-actions text-center">
                          <a class="btn btn-primary btn-sm" href="/admin/auth/user/">
                              <i class="fas fa-folder"></i>
                              View
                          </a>
                          <a class="btn btn-info btn-sm" href="/user/edit/{{ user.pk }}">
                              <i class="fas fa-pencil-alt"></i>
                              Edit
                          </a>

                          <button type="button" class="btn btn-danger btn-sm" data-toggle="modal" data-target=".mm-modal-{{user.pk}}">
                            <i class="fas fa-trash"></i>
                              Delete
                          </button>

                      </td>

                    </tr>

                      <div class="modal modal-danger fade mm-modal-{{ user.pk }}" id="modal-danger">
                        <div class="modal-dialog">
                          <div class="modal-content bg-danger">
                            <div class="modal-header">
                              <h4 class="modal-title">Are you sure?</h4>
                              <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                <span aria-hidden="true">&times;</span>
                              </button>
                            </div>
                            <div class="modal-body">
                              <p>Delete User&hellip;</p>
                            </div>
                            <div class="modal-footer justify-content-between">
                              <button type="button" class="btn btn-outline-light" data-dismiss="modal">Close</button>
                              <button type="button" onclick="DeleteID({{ user.pk }});" class="btn btn-outline-light">Delete user</button>
                            </div>
                          </div>
                        </div>
                      </div>



                  {% endfor %}

              {% endfor %}

              </tbody>
          </table>
        </div>
       
      </div>
      
    </div>

-->
{% endblock maincontent %}

{% block javascripts %}
<script>
  $(function () {
    $("#userlist").DataTable({
      "paging": true,
      "searching": true,
      "responsive": true, 
      "lengthChange": false, 
      "autoWidth": false,
      "buttons": ["copy", "csv", "excel", "pdf", "print", "colvis"]
    }).buttons().container().appendTo('#userlist_wrapper .col-md-6:eq(0)');
  });
</script>

<script>
  function DeleteID(del_id) {
        $.ajax({
            type: 'POST',
            url: "/configuration/user/delete/",
            headers:{
              "X-CSRFToken": '{{ csrf_token }}'
            },
            data : { 'delete_id': del_id},

            success : function(json) {
              document.location.href = "/user/"; 
            }
        })
  }
</script>

{% endblock javascripts %}
embark/templates/user/login.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/login.css' %}"/>{% endblock style %}
{% block title %}EMBArk login{% endblock title %}
{% block maincontent %}
    <div class="login-form-container">
        {% if error_message %}
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
            <medium>{{ message }}</medium>
        </div>
        {% endif %}
        {% if success_message %}
        <div class="alert alert-success alert-dismissible fade show" role="alert">
            <medium>{{ message }}</medium>
        </div>
        {% endif %}
        <div id="embarkLogo"><img src="{% static 'content/images/embark_logo.svg' %}" alt="EMBArk logo graphic" height="auto" width="auto"/></div>
        <div class="login">
            <form action="{% url 'embark-login' %}" class="login-form" method="post">
                {% csrf_token %}
                <h2 class="title">Sign in</h2>
                <div class="input-field" data-error="Username is required">
                    <svg viewBox="0 -2 25 25" xmlns="http://www.w3.org/2000/svg"><path d="m7.5.5c1.65685425 0 3 1.34314575 3 3v2c0 1.65685425-1.34314575 3-3 3s-3-1.34314575-3-3v-2c0-1.65685425 1.34314575-3 3-3zm7 14v-.7281753c0-3.1864098-3.6862915-5.2718247-7-5.2718247s-7 2.0854149-7 5.2718247v.7281753c0 .5522847.44771525 1 1 1h12c.5522847 0 1-.4477153 1-1z" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" transform="translate(3 2)"/></svg>
                    <input type="text" placeholder="Username" name="username" required/>
                </div>
                <div class="input-field">
                    <svg viewBox="0 -2 25 25" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" transform="translate(4 1)"><path d="m2.5 8.5-.00586729-1.99475098c-.00728549-4.00349935 1.32800361-6.00524902 4.00586729-6.00524902s4.0112203 2.00174967 4.0000699 6.00524902v1.99475098m-8.0000699 0h8.0225317c1.0543618 0 1.9181652.81587779 1.9945143 1.8507377l.0054778.1548972-.0169048 6c-.0031058 1.1023652-.8976224 1.9943651-1.999992 1.9943651h-8.005627c-1.1045695 0-2-.8954305-2-2v-6c0-1.1045695.8954305-2 2-2z" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="6.5" cy="13.5" fill="#000" r="1.5"/></g></svg>
                    <input type="password" placeholder="Password" name="password" required/>
                </div>
                <input id="loginButton" type="submit" class="solid btn-login" value="Login" />
            </form> 
        </div>
        <div id="login_footer">
            <a href="{% url 'embark-register' %}">
                <input class="solid btn-login" type="submit" value="Register" />
            </a>
        </div>
    </div>
{% endblock maincontent %}
embark/templates/user/log.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/log.css' %}"/>{% endblock style %}
{% block title %}EMBArk log{% endblock title %}
{% block navigation %}{% include "navigation.html" %}{% endblock navigation %}
{% block maincontent %}
    <div id="logHeader"><strong>{{ header }}</strong></div>
    <div id="logBox">
    {{ log }}
    </div>
{% endblock maincontent %}

embark/templates/user/accountDelete.html METASEP
{% extends "base.html" %}
{% block title %} Delete Account {% endblock title %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/passwordReset.css' %}"/>{% endblock style %}
{% block navigation %}{% include "navigation.html" %}{% endblock navigation %}
{% block maincontent %}

<div class="auth-wrapper">
                <form action="{% url 'embark-acc-delete' %}" method="post" novalidate>
                    <div class="box box-primary">
                            <div class="box-header">
                            <h2 class="page-header"><i class="fa fa-lock"></i> Permanently delete my Account </h2>
                        </div>
                        <div class="box box-body-second">
                            <p>Are you sure you want to permanently delete your account?</p>
                        </div>
                        <div class="btn-cnt">
                            <p>
                                <button class="btn-reset" type="submit" name="DeleteAcc">Delete Account</button>
                            </p>
                        </div>
                    </div>
                </form>
</div>

{% endblock maincontent %}
embark/templates/uploader/firmwareDelete.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/fileUpload.css' %}"/>{% endblock style %}
{% block js %}<script type="text/javascript" src="{% static 'scripts/fileUpload.js' %}"></script>{% endblock js %}
{% block title %}EMBArk file upload{% endblock title %}
{% block navigation %}{% include "navigation.html" %}{% endblock navigation %}
{% block maincontent %}
{% if success_message %}
    <div class="alert alert-success alert-dismissible fade show" role="alert">
        <medium>{{ message }}</medium>
    </div>
{% endif %}
    <div class="row DeleteRow">
            <div class="box">
                <p class="mainText">Select and delete a firmware file</p>
                <form action="{% url 'embark-uploader-delete' %}" method="post" id="delete-form">
                    <!--  onsubmit="window.confirmDelete(event)" -->
                    {% csrf_token %}
                    <div class="innerBlock">
                        <div id="firmwareToDelete" class="form-group deleteFirmware" >
                            {{ form }}
                        </div>
                    </div>
                    <div class="buttonRow">
                        <button type="delete" class="btn buttonRowElem">Delete</button>
                    </div>
                </form>
            </div>
    </div>
{% endblock maincontent %}

embark/templates/uploader/fileUpload.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/fileUpload.css' %}"/>{% endblock style %}
{% block js %}<script type="text/javascript" src="{% static 'scripts/fileUpload.js' %}"></script>{% endblock js %}
{% block title %}EMBArk file upload{% endblock title %}
{% block navigation %}{% include "navigation.html" %}{% endblock navigation %}
{% block maincontent %}
<!-- success_message, message, analyze_form, -->
{% if success_message %}
    <div class="alert alert-success alert-dismissible fade show" role="alert">
        <medium>{{ message }}</medium>
    </div>
{% endif %}
    <div class="row uploadRow">
       
        <div class="col-sm">
            <div class="box">
                <p class="mainText">Upload firmware image/archive</p>
                <p class="subText">Supported archive types : firmware binary and most archive types</p>
                <div class="innerBlock">
                    <!--TODO display filename if one is selected or make select upload directly-->
                    Selected file:
                    <div id="file-name" class="uploadedFile">None</div>
                </div>
                <div class="buttonRow">
                    <button type="submit" id="uploadFirmware-btn" class="btn buttonRowElem" onclick="saveFiles()" disabled>
                        Upload
                    </button>
                    <button type="button" class="btn buttonRowElem" >
                        <label for="file-input">Select file</label>
                    </button>
                    <input id="file-input" type="file" multiple/>
                </div>
                <div class="progress-wrapper" id="progress-wrapper">
                    <div class="progress-bar" id="progressBar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
                        0%
                    </div>
                </div>
            </div>
        </div>

        <div class="col-sm">
            <div class="box">
                <p class="mainText">Analyze a firmware image/archive</p>
                <form action="{% url 'embark-uploader-start-analysis' %}" method="post" id="analyze-form">
                    {% csrf_token %}
                    <div class="innerBlock">
                        <label for="expertMode">Expert mode</label>
                        <label class="switch">
                            <input id="expertModeSwitch" type="checkbox" onclick="expertModeOn()"/>
                            <span class="slider round"></span>
                        </label>

                        {% load filters %}
                        <!-- Iterate over all fields and set expmode on fields in expert mode -->
                        {% for field in analysis_form %}

                            {% if field.expert_mode %}
                                <div class="form-group expertModeOptions" value="expmode_off">
                                    {% if field.field.widget|is_checkbox %}
                                        {{ field }}
                                        {{ field.label_tag }}
                                    {% else %}
                                        {% comment %} {{ field.label_tag }} {% endcomment %}
                                        {{ field }}
                                    {% endif %}
                                    {% if field.help_text %}
                                        <small class="form-text text-muted" value="help_text_off">{{ field.help_text }}</small>
                                    {% endif %}
                                </div>
                            {% else %}
                                <div class="form-group">
                                    {% if field.field.widget|is_checkbox %}
                                        {{ field }}
                                        {{ field.label_tag }}
                                    {% else %}
                                        {% comment %} {{ field.label_tag }} {% endcomment %}
                                        {{ field }}
                                    {% endif %}
                                    {% if field.help_text %}
                                        <small class="form-text text-muted" value="help_text_off">{{ field.help_text }}</small>
                                    {% endif %}
                                </div>
                            {% endif %}

                        {% endfor %}

                    </div>
                    <div class="buttonRow">
                        <button type="submit" class="btn buttonRowElem">Analyze</button>
                        <button type="reset" class="btn buttonRowElem">Cancel</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
{% endblock maincontent %}

embark/templates/dashboard/serviceDashboard.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/serviceDashboard.css' %}"/>{% endblock style %}
{% block js %}<script type="text/javascript" src="{% static 'scripts/serviceDashboard.js' %}"></script>{% endblock js %}
{% block title %}EMBArk service dashboard{% endblock title %}
{% block navigation %}{% include "navigation.html" %}{% endblock navigation %}
{% block maincontent %}
{% if success_message %}
    <div class="alert alert-success alert-dismissible fade show" role="alert">
        <medium>{{ message }}</medium>
    </div>
{% endif %}
    <div class="row StopRow">
            <div class="box">
                <p class="mainText">Select and stop Analysis</p>
                <form action="{% url 'embark-stop-analysis' %}" method="post" id="stop-form">
                    <!--  onsubmit="window.confirmDelete(event)" -->
                    {% csrf_token %}
                    <div class="innerBlock">
                        <div id="analysisToStop" class="form-group stopAnalysis" >
                            {{ form }}
                        </div>
                    </div>
                    <div class="buttonRow">
                        <button type="stop" class="btn buttonRowElem">Stop</button>
                    </div>
                </form>
            </div>
    </div>
{% endblock maincontent %}

embark/templates/dashboard/reportDashboard.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/reportDashboard.css' %}"/>{% endblock style %}
{% block title %}EMBArk report dashboard{% endblock title %}
{% block navigation %}{% include "navigation.html" %}{% endblock navigation %}
{% block maincontent %}
    <div class="row reportRow">
    <div class="col-sm" id="reportDashboardMainContainer">

            {% csrf_token %}
            <!-- Iterate over all fields and set expmode on fields in expert mode -->
            <div class="col-sm-2 input-group float-right searchBar">
                <input type="text" class="form-control input-lg" placeholder="Search..." id="mySearchText"/>
                <span class="input-group-btn">
                    <button class="btn btn-info btn-sm" type="submit" id="mySearchButton">
                        <svg height="21" viewBox="0 0 21 21" width="21" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><circle cx="8.5" cy="8.5" r="5"/><path d="m17.571 17.5-5.571-5.5"/></g></svg>
                    </button>
                </span>
            </div>
            <div class="tableData">
                <table class="table table-striped table-light table-hover" id="report-table">
                    <thead>
                    <tr>
                        <th data-priority="2" scope="col">ID</th>
                        <th scope="col">Name</th>
                        <th scope="col">Vendor</th>
                        <th scope="col">Device</th>
                        <th scope="col">Version</th>
                        <th scope="col">Start Date</th>
                        <th scope="col">End Date</th>
                        <th data-orderable="false" data-priority="1" scope="col">Actions</th>
                    </tr>
                    </thead>

                    <tbody>
                    {% for firmware in finished_firmwares %}
                        <tr>
                            <td>{{ firmware.id }}</td>
                            <td>{{ firmware.firmware }}</td>
                            <td>{{ firmware.vendor }}</td>
                            <td>{{ firmware.device }}</td>
                            <td>{{ firmware.version }}</td>
                            <td>{{ firmware.start_date }}</td>
                            <td>{{ firmware.end_date }}</td>
                            <td>
                                <div class="buttonRow">
                                    <form action={% url 'embark-download' firmware.id %} method='get'>
                                        <button type="submit" class="btn buttonRowElem" >Download Logs</button>
                                    </form>
                                    <form action={% url 'embark-html-report' firmware.id 'index.html' %} method='get'>
                                        <button type="submit" class="btn buttonRowElem" >Open Report</button>
                                    </form>
                                    <form action={% url 'embark-IndividualReportDashboard' firmware.id %} method='get'>
                                        <button type="submit" class="btn buttonRowElem" >Detail View</button>
                                    </form>
                                    <form action={% url 'embark-uploader-home' %} method='get'>
                                        <button type="submit" class="btn buttonRowElem" >Rescan/delete</button>
                                    </form>
                                </div>
                            </td>
                        </tr>
                    {% endfor %}
                    </tbody>
                </table>
            </div>

        </div>
    </div>
{% endblock maincontent %}
{% block inlinejs %}
    <script>
        $(document).ready(function() {
            let table = $('#report-table').DataTable({sDom: 'lrtip'});

            $('#mySearchButton').on( 'keyup click', function () {
                table.search($('#mySearchText').val()).draw();
            } );

            $("#mySearchText").change(function(){
                table.search($('#mySearchText').val()).draw();
            });
        } );

    </script>
{% endblock inlinejs %}

embark/templates/dashboard/mainDashboard.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/mainDashboard.css' %}"/>{% endblock style %}
{% block title %}EMBArk main dashboard{% endblock title %}
{% block navigation %}{% if nav_switch %}{% include "navigation.html" %}{% endif %}{% endblock navigation %}
{% block maincontent %}
    <div class="cardBox">
        <div class="card">
            <div class="card-title cardName firmwareAnalysed">Firmware tested</div>
            <div class="card-body cardValue" id="firmwareAnalysed"></div>
        </div>
        <div class="card">
            <div class="card-title cardName totalFiles">Files analysed</div>
            <div class="card-body cardValue" id="totalFiles"></div>
        </div>
        <div class="card">
            <div class="card-title cardName totalDirectories">Directories found</div>
            <div class="card-body cardValue" id="totalDirectories"></div>
        </div>
        <div class="card">
            <div class="card-title cardName totalBinaries">Binaries analysed</div>
            <div class="card-body cardValue" id="totalBinaries"></div>
        </div>
        <div class="card">
            <div class="card-title cardName totalCve">Vulnerabilities (CVEs)</div>
            <div class="card-body cardValue" id="totalCve"></div>
        </div>
        <div class="card">
            <div class="card-title cardName totalIssues">Exploits</div>
            <div class="card-body cardValue" id="totalIssues"></div>
        </div>
    </div>

    <div class="dataCard">
        <div class="row cardRow dataCardRow">
            <div class="card card-left">
                <canvas class="aggregatedReport" id="accumulatedCvePie"></canvas>
            </div>
            <div class="card card-right">
                <canvas class="aggregatedReport" id="accumulatedOs"></canvas>
            </div>
        </div>
    </div>
    <div class="dataCard">
        <div class="row cardRow dataCardRow2">
            <div class="card card-pie">
                <div class="row">
                    <canvas class="aggregatedReport" id="nxpie"></canvas>
                </div>
            </div>
            <div class="card card-pie">
                <div class="row">
                    <canvas class="aggregatedReport" id="piepie"></canvas>
                </div>
            </div>
            <div class="card card-pie">
                <div class="row">
                    <canvas class="aggregatedReport" id="relropie"></canvas>
                </div>
            </div>
            <div class="card card-pie">
                <div class="row">
                    <canvas class="aggregatedReport" id="canarypie"></canvas>
                </div>
            </div>
            <div class="card card-pie">
                <div class="row">
                    <canvas class="aggregatedReport" id="strippedpie"></canvas>
                </div>
            </div>
        </div>
    </div>

    <div class="dataCard">
        <div class="row cardRow dataCardRow3">
            <div class="card card-wide">
                <canvas class="system-overload" id="loadChart"></canvas>
            </div>
        </div>
    </div>

    <div class="dataCard">
        <div class="row cardRow dataCardRow4">
            <div class="card">
                <canvas class="aggregatedReport" id="accumulatedArchitecture"></canvas>
            </div>
            <div class="card" id="entropy">
                <div class="topEntropyGraph">
                    <canvas class="aggregatedReport" id="topEntropies"></canvas>
                </div>
                <label for="accumulatedEntropy" id="entropyMeterLabel">Average Entropy Value:</label>
                <meter id="accumulatedEntropy"
                    min="0" max="8"
                    value="0"
                    high="6"
                    low="3">
                </meter>
            </div>
        </div>
        <div class="row cardRow dataCardRow5">
            <div class="card card-wide">
                <canvas class="aggregatedReport" id="topBinaryTypes"></canvas>
            </div>
        </div>
    </div>
{% endblock maincontent %}
{% block inlinejs %}
    <script type="text/javascript" src="{% static 'scripts/mainDashboard.js' %}"></script>
    <script type="text/javascript" src="{% static 'scripts/accumulatedReports.js' %}"></script>
{% endblock inlinejs %}

embark/templates/dashboard/individualReportDashboard.html METASEP
{% extends "base.html" %}
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/individualReportDashboard.css' %}"/>{% endblock style %}
{% block title %}EMBArk individual dashboard{% endblock title %}
{% block navigation %}{% include "navigation.html" %}{% endblock navigation %}
{% block maincontent %}
  <div>
    <h1><center>Firmware security scanning details</center></h1>
  </div>
  <div class="dataCard">
      <div class="row dataCardIRRow1">
          <div class="leftCol">
              <div class="card">
                <canvas class="aggregatedReport" id="accumulatedCveDoughnut"></canvas>
              </div>
              <div class="card IRReportCard">
                <canvas class="aggregatedReport" id="nxpie"></canvas>
              </div>
              <div class="card IRReportCard">
                <canvas class="aggregatedReport" id="piepie"></canvas>
              </div>
              <div class="card IRReportCard">
                <canvas class="aggregatedReport" id="relropie"></canvas>
              </div>
              <div class="card IRReportCard">
                <canvas class="aggregatedReport" id="canarypie"></canvas>
              </div>
              <div class="card IRReportCard">
                <canvas class="aggregatedReport" id="strippedpie"></canvas>
              </div>
          </div>

          <div class="card dataCardIR">
            <table class="table table-striped table-borderless table-individualrep">
              <div class="buttonRow">
              <!-- Quick and dirty JS solution. Need to fix the report id for using django mechanisms -->
              <form action={% url 'embark-html-report' analysis_id 'index.html' %} method='get'>
                <button class="btn buttonRowElem" type="submit">Open Report</button>
              </form>
              <form action={% url 'embark-download' analysis_id %} method='get'>
                <button class="btn buttonRowElem" type="submit">Download Report</button>
              </form>
              <!-- TODO add another uploader/start pattern for firmware_id-->
              <form action={% url 'embark-uploader-start-analysis' %} method='get'>
                <button type="submit" class="btn buttonRowElem" >Rescan</button>
              </form>
              <form action={% url 'embark-uploader-delete' %} method='get'>
                <button type="submit" class="btn buttonRowElem" >Delete Firmware</button>
              </form>
              <form action={% url 'embark-MainDashboard' %} method='get'>
                <button type="submit" class="btn buttonRowElem" >Main dashboard</button>
              </form>
              </div>
              <br/>
              <tbody id="detail_body"></tbody>
            </table>
            <div>
              <div>Firmware entropy:</div>
              <img id="entropy" src={% static 'content/images/header.png' %} alt="header" height="auto" width="auto"/>
            </div>
          </div>
      </div> 
  </div>
{% endblock maincontent %} 
{% block inlinejs %}
<script type="text/javascript" src="{% static 'scripts/individualReportDashboard.js' %}"></script>
{% endblock inlinejs %}

embark/static/scripts/serviceDashboard.js METASEP
// jshint unused:false
// ^ this should only be added AFTER successfull check (disables waring for global functions)

/**
 * Update the Progress bar with the percentange of progress made in Analysing the Firmware
 * @param {*} percent Percentage Completed
 * @param {*} cur_ID Current Id of the Container
 */
 function makeProgress(percent, cur_ID) {
    "use strict";
    var p = percent * 100;
    var rounded = Math.round(p);
    var id = "#pBar_" + cur_ID;
    $(id).attr('aria-valuenow', rounded).css('width', rounded + '%').text(rounded + '%');
}

/**
 * Bind the Phase Messages from log file to Container
 * @param {*} phase Phase Message received from Log
 * @param {*} cur_ID Current Id of the Container
 */
function livelog_phase(phase, cur_ID) {
    "use strict";
    var id = "#log_phase_" + cur_ID;
    var $List = $(id);
    var $entry = $('<li>' + phase + '</li>');
    $List.append($entry);
}

/**
 * Bind the Module message from log file to container
 * @param {*} module Module Log message received from Log
 * @param {*} cur_ID Current Id of the container
 */
function livelog_module(module, cur_ID) {
    "use strict";
    var id = "#log_module_" + cur_ID;
    var $List = $(id);
    var $entry = $('<li>' + module + '</li>');
    $List.append($entry);
}

function getCookie(name) {
    "use strict";
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
  
/**
 * Removes the container from the UI
 * @param {*} currentID Id of the container which is passed backend to pull information
 */
function cancelLog(currentID) {
    "use strict";
    try {
        var idOfDIV = "#Container_" + currentID;
        $(idOfDIV).remove();
    } catch (error) {
        //console.log(error.message);
        console.log(error);
    }
}

/**
 * simple redirect to hashid associated with currentID
 * @param {*} currentID Id of the contaniner which is passed backend to pull information
 */
 function viewLog(currentID) {
    "use strict";
    try {
        // TODO get hashid of div-id
        window.location("/log/" + currentID);
    } catch (error) {
        //console.log(error.message);
        console.log(error);
    }
}

/**
 *  start socket connection just once TODO wss compatible? 
 */
var loc = window.location;
var wsStart = 'ws://';
var wsPort = ':8001';
if (loc.protocol == 'https:') {
      wsStart = 'wss://';
      wsPort = ':8000';
}
var socket = new WebSocket(
        wsStart + location.hostname + wsPort + '/ws/progress/'
);
/*for log implementation which is currently commented out*/
var module_array = [];
var phase_array = [];
var cur_len = 0;

/**
 * called when a websocket connection is established
 * */
socket.onopen = function () { 
    "use strict";
    console.log("[open] Connection established");
};

/**
 * This method is called whenever a message from the backend arrives
 * */
socket.onmessage = function (event) {
    "use strict";
    try{
            var data = JSON.parse(event.data);
            console.log(data);

            if (cur_len !== Object.keys(data).length) {
                var htmlToAdd = `
                <div class="box" id="Container_` + Object.keys(data)[cur_len] + `">
                    <div class="mainText">
                        <span>`+data[Object.keys(data)[cur_len]][0].firmwarename.split(".")[0]+`</span>
                    </div>
                    <div class="row">
                        <div class="col-sm log tile moduleLog">
                            <ul class="log_phase logUL" id="log_phase_` + Object.keys(data)[cur_len] + `"></ul>
                        </div>
                        <div class="col-sm log tile phaseLog">
                            <ul class="log_phase logUL" id="log_module_` + Object.keys(data)[cur_len] + `"></ul>
                        </div>
                    </div>
                    <div id="progress-wrapper">
                        <div id="pBar_` + Object.keys(data)[cur_len] + `" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
                                0 % 
                        </div>
                    </div>
                </div>
                <div class="buttonRow">
                    <!--
                    <button type="view-log" class="btn buttonRowElem" id="` + Object.keys(data)[cur_len] + `" onclick="viewLog(this.id)" >
                        EMBA-log
                    </button>
                    <button type="reset" class="btn buttonRowElem" id="` + Object.keys(data)[cur_len] + `" onclick="cancelLog(this.id)" >
                        Cancel
                    </button>
                    -->
                </div>`;
                //Todo: on non service dashboard web sites we get an exception:
                document.getElementsByClassName("main")[0].insertAdjacentHTML('beforeend', htmlToAdd);
                console.log("log_phase_" + Object.keys(data)[cur_len]);
                module_array.push("no module");
                phase_array.push("no phase");
                cur_len += 1;
            }

            for (let idx = 0; idx < cur_len; idx++) {
                let id = Object.keys(data)[idx];
                var length = data[id].length;
                if (phase_array[idx] !== data[id][length - 1].phase) {
                    console.log(data[id][length - 1].phase);
                    livelog_phase(data[id][length - 1].phase, id);
                }
                if (module_array[idx] !== data[id][length - 1].module) {
                    livelog_module(data[id][length - 1].module, id);
                }
                module_array[idx] = data[id][length - 1].module;
                phase_array[idx] = data[id][length - 1].phase;
                if (Number(data[id][length - 1].percentage) > Number(1)) {
                  console.log("progress bar overload detected: " + data[id][length - 1].percentage);
                  makeProgress(1.00, id);
                } else {
                  makeProgress(data[id][length - 1].percentage, id);
                }
            }
    }
    catch(error){
        console.log(error);
    }
};

/**
 * This method is called when the websocket connection is closed
 *  */
socket.onclose = function () {
    "use strict";
    // console.error('Chat socket closed unexpectedly', event);
    console.log("[Socket]Closed Successfully");
};

/**
 * this method is called when an error occurs
 *  */
socket.onerror = function (err) {
    "use strict";
    //console.error('Socket encountered error: ', err.message, 'Closing socket');
    console.error('Socket encountered error: ', err);
    socket.close();
};

/* /**
 * Connection Established
 
function embaProgress() {
    console.log("Messaging started")
    setInterval(function () {
        socket.send("Hello");
    }, 3000);
}
 */


embark/static/scripts/mainDashboard.js METASEP
// jshint unused:false
// ^ this should only be added AFTER successfull check (disables waring for global functions)

/**
 * Get Load of Time , CPU and Memory Percentage
 * @returns Object of the Time,cpu and memory Percentage
 */
 function get_load() {
    "use strict";
    try {
        let url = window.location.origin + "/get_load/";

        return $.getJSON(url).then(function (data) {

            return {
                time: data.timestamp,
                cpu: data.cpu_percentage,
                mem: data.memory_percentage
            };
        });
    } catch (error) {
        console.log(error.message);
    }
}

/**
 * Validates the Login
 * @returns HTTP Response
 */
function check_login() {
    "use strict";
    let url = window.location.origin + "/check_login/";
    return $.getJSON(url);
}

//check_login();

try {
    var loadChart = document.getElementById('loadChart').getContext('2d');

    /**
     * Develops Chart after loading the required data
     */
    get_load().then(function (returndata) {
        "use strict";
        let lineChart = new Chart(loadChart, {
            type: 'line', // bar, horizontalBar, pie, line, doughnut, radar, polarArea
            data: {
                labels: returndata.time,
                datasets: [{
                        label: 'CPU',
                        data: returndata.cpu,
                        borderColor: 'rgba(255, 127, 64, 1)',
                        backgroundColor: 'rgba(255, 127, 64, 0.2)',
                        borderWidth: 2,
                        hoverBorderWidth: 8,
                        hoverBorderColor: 'rgba(255, 127, 64, 1)',
                        fill: true,
                        cubicInterpolationMode: 'monotone'
                    },
                    {
                        label: 'MEM',
                        data: returndata.mem,
                        borderColor: 'rgba(64,127,255,1)',
                        backgroundColor: 'rgba(64,127,255, 0.2)',
                        borderWidth: 2,
                        hoverBorderWidth: 8,
                        hoverBorderColor: 'rgba(64,127,255,1)',
                        fill: true,
                        cubicInterpolationMode: 'monotone'
                    }
                ]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                title: {
                    display: false,
                    text: 'CPU / Memory utilization percentage',
                    fontSize: 25
                },
                legend: {
                    display: false,
                    position: 'right',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                scales: {
                    x: {
                        ticks: {
                            display: true,
                            autoSkip: false,
                            maxRotation: 0,
                            minRotation: 0,
                            callback: function(val, index) {
                                // Hide the label of every 2nd dataset
                                return index % 20 === 0 ? this.getLabelForValue(val).split('T')[1].split('.')[0] : '';
                              },
                        }
                    },
                    y: {
                        min: 0,
                        max: 100,
                        stepSize: 5
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        });
    });

} catch (error) {
  // console.log("exception" + error)
}


embark/static/scripts/main.js METASEP
// jshint unused:false
// ^ this should only be added AFTER successfull check (disables warning for global functions)

/**
 * Activate Navigation Menu
 */
function navToggle() {
    "use strict";
    document.getElementById("navigation").classList.toggle("active");
}

/**
 * To display the individual helptext of form fields below
 */
 function helpTextOn() {
    "use strict";
    try {
        var expertOptions = document.querySelectorAll('small.text-muted');

        for (var i = 0; i < expertOptions.length; i++) {
            var expertDiv = expertOptions[i];
            if (expertDiv.getAttribute("value") === "help_text_off") {
                expertDiv.setAttribute("value","help_text_on");
            } else {
                expertDiv.setAttribute("value","help_text_off");
            }
        }
    } catch (error) {
        console.log(error.message);
    }
}

/**
 * To toggle expert mode option during analysing the Firmware
 */
function expertModeOn() {
    "use strict";
    try {
        var expertOptions = document.querySelectorAll('div.expertModeOptions');

        for (var i = 0; i < expertOptions.length; i++) {
            var expertDiv = expertOptions[i];
            if (expertDiv.getAttribute("value") === "expmode_off") {
                expertDiv.setAttribute("value","expmode_on");
            } else {
                expertDiv.setAttribute("value","expmode_off");
            }
        }
    } catch (error) {
        console.log(error.message);
    }
    /* we enable the help text automatically in expert mode */
    helpTextOn();
}

/**
 * To show a window on confirmation screen asking the user to progress
 * @param {*} event Event Object which provides the firmware Value
 */
function confirmDelete(event) {
    "use strict";
    var isValid = confirm(`Are you sure to delete the following firmware file: ${event.target.elements.firmware.value} ?`);
    if (!isValid) {
        event.preventDefault();
          console.log("deletion cancelled");
    } else {
        successAlert(`firmware file deleted: ${event.target.elements.firmware.value}`);
    }
}

embark/static/scripts/individualReportDashboard.js METASEP
// jshint unused:false
// ^ this should only be added AFTER successfull check (disables waring for global functions)

var accumulatedCveDoughnut = document.getElementById('accumulatedCveDoughnut').getContext('2d');

var nxpie = document.getElementById('nxpie').getContext('2d');
var piepie = document.getElementById('piepie').getContext('2d');
var relropie = document.getElementById('relropie').getContext('2d');
var canarypie = document.getElementById('canarypie').getContext('2d');
var strippedpie = document.getElementById('strippedpie').getContext('2d');

let report_id = window.location.pathname.split("/").pop();
let entropy_url = "/emba_logs/REPORT_ID_REPLACE/emba_logs/html-report/style/firmware_entropy.png".replace(/REPORT_ID_REPLACE/,report_id);
document.getElementById("entropy").src = entropy_url;

/**
 * Gets data to generate Reports for Individual Firmware
 * @returns data of the Nalysed Individual firmware
 */
 function get_individual_report() {
    "use strict";
    let report_index = window.location.href.substring(window.location.href.lastIndexOf('/') + 1);
    let url = window.location.origin + "/get_individual_report/" + report_index;

    return $.getJSON(url).then(function (data) {
        return data;
    });
}

/**
 * Develops Chart
 * @param {*} html_chart Type of Chart
 * @param {*} label_1 Labels
 * @param {*} label_2 Labels
 * @param {*} color_1 Colors
 * @param {*} color_2 Colors
 * @param {*} data_cmp Data to be plotted
 * @param {*} data_strcpy
 * @param {*} title Title of The chart
 */
function make_chart(html_chart, label_1, label_2, color_1, color_2, data_cmp, data_strcpy, title) {
    "use strict";
    let chart = new Chart(html_chart, {
        type: 'pie',
        data: {
            labels: [label_1, label_2],
            datasets: [{
                labels: [label_1, label_2],
                data: [data_strcpy, (data_cmp - data_strcpy)],
                backgroundColor: [color_1, color_2],
            }, ],
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: title,
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        }
    });
}

/**
 * Generates Reports after you complete receiving the data for Individual Fimware
 */
get_individual_report().then(function (returnData) {
    "use strict";
    let cvedoughnutChart = new Chart(accumulatedCveDoughnut, {
        type: 'doughnut',
        data: {
            labels: [
                'CVE-High',
                'CVE-Low',
                'CVE-Medium'
            ],
            datasets: [{
                label: 'CVE DATA',
                data: [returnData.cve_high, returnData.cve_low, returnData.cve_medium],
                backgroundColor: [
                    'rgb(255, 99, 132)',
                    'rgb(54, 162, 235)',
                    'rgb(255, 205, 86)'
                ],
                hoverOffset: 4
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: 'CVE Data',
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        }
    });

    make_chart(relropie, 'Binaries without RELRO', 'Binaries with RELRO',
        '#493791', '#291771', returnData.bins_checked, returnData.relro, 'RELRO');
    make_chart(nxpie, 'Binaries without NX', 'Binaries with NX',
        '#1b1534', '#000014', returnData.bins_checked, returnData.no_exec, 'NX');
    make_chart(piepie, 'Binaries without PIE', 'Binaries with PIE',
        '#7b919d', '#5b717d', returnData.bins_checked, returnData.pie, 'PIE');
    make_chart(canarypie, 'Binaries without CANARY', 'Binaries with CANARY',
        '#525d63', '#323d43', returnData.bins_checked, returnData.canary, 'CANARY');
    make_chart(strippedpie, 'Stripped binaries', 'Unstripped binaries',
        '#009999', '#005050', returnData.bins_checked, returnData.stripped, 'Stripped');

    let data_to_display = {
        "Firmware name": returnData.name.replace(/\d\//,""),
        "Firmware ID": returnData.name.replace(/\/.*$/,""),
        "Start date": returnData.start_date.replace('T', ' - '),
        "End date": returnData.end_date.replace('T', ' - '),
        "Scan Duration": returnData.duration,
        "Vendor": returnData.vendor,
        "Version": returnData.version,
        "Notes": returnData.notes,
        "Operating sytem detected": returnData.os_verified,
        "Architecture detected": returnData.architecture_verified,
        "Entropy value": returnData.entropy_value,
        "Path to logs": returnData.path_to_logs,
        "EMBA command": returnData.emba_command,
        "Files detected": returnData.files,
        "Directories detected": returnData.directories,
        "Certificates detected": returnData.certificates,
        "Outdated certificates detected": returnData.certificates_outdated,
        "Shell scripts detected": returnData.shell_scripts,
        "Shell script issues": returnData.shell_script_vulns,
        "Binaries checked": returnData.bins_checked,
        "Versions identified": returnData.versions_identified,
        "Exploits identified": returnData.exploits,
        "Metasploit modules": returnData.metasploit_modules,
        "High CVE": returnData.cve_high,
        "Medium CVE": returnData.cve_medium,
        "Low CVE": returnData.cve_low,
        "NX disabled binaries": returnData.nx,
        "RELRO disabled binaries": returnData.relro,
        "PIE disabled binaries": returnData.pie,
        "Stack canaries disabled binaries": returnData.canary,
        "Stripped binaries": returnData.stripped,
    };

    for (const [key, value] of Object.entries(returnData.strcpy_bin)) {
        data_to_display["STRCPY binary: " + key] = value;
    }

    const table = document.getElementById("detail_body");
    for (const [key, value] of Object.entries(data_to_display)) {
        let row = table.insertRow();
        let date = row.insertCell(0);
        date.innerHTML = key;
        let name = row.insertCell(1);
        name.innerHTML = value;
    }
});
embark/static/scripts/fileUpload.js METASEP
// jshint unused:false
// ^ this should only be added AFTER successful check (disables warning for global functions)
/**
 * The following event calls prevent default to turn off the browsers default drag and drop handler
 * @param {*} ev Event
 */
function dragOverHandler(ev) {
  "use strict";
  ev.preventDefault();
}

function getCookie(name) {
    "use strict";
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

$(window).bind("load", function() {
    "use strict";
    try{
        document.querySelector("#file-input").onchange = function(){
            var fileNames = "";
            for (var i = 0; i < this.files.length; i++) {
                fileNames = fileNames + this.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + "<br>";
            }
            var target = document.querySelector("#file-name");
            target.innerHTML = fileNames;
            $("#uploadFirmware-btn").attr("disabled", false);
        };
    }catch (error){
        console.log(error.message);
    }
});

/**
 * Makes Ajax call and save files locally
 * @param {*} formData Information of the uploaded file or Files
 */
 async function postFiles(formData) {
  "use strict";
  try {
    //formData.append('file', fileData);
    $.ajax({
      type: 'POST',
      url: '/uploader/save/',
      data: formData,
      headers: {
        'X-CSRFToken': getCookie('csrftoken')
      },
      processData: false,
      contentType: false,
      xhr: function () {
        var xhr = new window.XMLHttpRequest();

        xhr.upload.addEventListener('progress', function (e) {
          if (e.lengthComputable) {

            var percent = Math.round(e.loaded / e.total * 100);

            $('#progressBar').attr('aria-valuenow', percent).css('width', percent + '%').text(percent + '%');
          }
        });
        return xhr;
      },
      success: function (data) {
        if (data == "File Exists") {
          var fileData = document.getElementById('file-input').files[0];
          var formData = new FormData();
          var res = confirm("A File with the same name exists ,Click ok to rename and save it");
          if (res == true) {
            var fileName = prompt("Please enter the new File name", fileData.inputFileName);
            if (fileName != null) {
              const myRenamedFile = new File([fileData], fileName);
              formData.append('file', myRenamedFile);
              postFiles(formData);
            }
          } else {
            console.log("The file is not saved");
            errorAlert("" + data);
          }
        } else {
          if(data === "successful upload"){
            location.href = "/uploader/start/";
          }
        }
      }
    });
  } catch (error) {
      console.log(error.message);
  }
}

/**
 * Checks for any Multiple uploads and the Passes to save
 */
function saveFiles() {
    "use strict";
    var progressBar = document.getElementById("progress-wrapper");
    if (progressBar.style.display == "none") {
      progressBar.style.display = "block";
    } else {
      progressBar.style.display = "none";
    }
    var fileData = document.getElementById('file-input').files;
    var formData = new FormData();
    for (let index = 0; index < fileData.length; index++) {
      fileData[index].inputFileName = fileData[index].name;
      formData.append('file', fileData[index]);
    }
  postFiles(formData);
}


embark/static/scripts/alertBox.js METASEP
// jshint unused:false
// ^ this should only be added AFTER successfull check (disables warning for global functions)

/** Error alert - Displays Alert message if something is failed
 * @param {*} message - message to be shown to user
 */
function errorAlert(message) {
    "use strict";
    $.confirm({
        title:  'Error!',
        content: message,
        type: 'red',
        typeAnimated: true,
        buttons: {
            close: function () {}
        }
    });

  }

  /**
   * Success Alert - Display success message to user
   * @param {*} message - Display message to be shown to user
   */
  function successAlert(message) {
    "use strict";
    $.confirm({
       title:  'Success',
       content: message,
       type: 'green',
       typeAnimated: true,
       buttons: {
           close: function () {
               location.reload();
           }
       }
   });
  }

  /**
   * For any input which is required to be entered by user .
   * @param {*} message Display message to user
   */
  function promptAlert(message) {
    "use strict";
    $.confirm({
      title: 'Prompt',
      content: '' +
      '<form action="" class="formName">' +
      '<div class="form-group">' +
      '<label>'+ message +'</label>' +
      '<input type="text" placeholder="Your name" class="name form-control" required />' +
      '</div>' +
      '</form>',
      buttons: {
          formSubmit: {
              text: 'Submit',
              btnClass: 'btn-blue',
              action: function () {
                  var name = this.$content.find('.name').val();
                  if(!name){
                      $.alert('Please fill the text ');
                      return false;
                  }
                  $.alert('You entered ' + name);
              }
          },
          cancel: function () {
          },
      },
      onContentReady: function () {
          var jc = this;
          this.$content.find('form').on('submit', function (e) {
              e.preventDefault();
              jc.$$formSubmit.trigger('click');
         });
      }
  });
  }
  

embark/static/scripts/accumulatedReports.js METASEP
// jshint unused:false
// ^ this should only be added AFTER successfull check (disables waring for global functions)

var nxpie = document.getElementById('nxpie').getContext('2d');
var piepie = document.getElementById('piepie').getContext('2d');
var relropie = document.getElementById('relropie').getContext('2d');
var canarypie = document.getElementById('canarypie').getContext('2d');
var strippedpie = document.getElementById('strippedpie').getContext('2d');

var accumulatedCvePie = document.getElementById('accumulatedCvePie').getContext('2d');
var accumulatedEntropy = document.getElementById('accumulatedEntropy');

var accumulatedArchitecture = document.getElementById('accumulatedArchitecture').getContext('2d');
var accumulatedOs = document.getElementById('accumulatedOs').getContext('2d');

var firmwareAnalysed = document.getElementById('firmwareAnalysed');
var totalFiles = document.getElementById('totalFiles');
var totalDirectories = document.getElementById('totalDirectories');
var totalBinaries = document.getElementById('totalBinaries');
var totalCve = document.getElementById('totalCve');
var totalIssues = document.getElementById('totalIssues');
var topEntropies = document.getElementById('topEntropies').getContext('2d');
var entropyMeterLabel = document.getElementById('entropyMeterLabel');

var topBinaryTypes = document.getElementById('topBinaryTypes').getContext('2d');


/**
 * Get Random Colors for the Charts .
 * @param {*} num Number of Colors required for the chart
 * @returns Array of colors with RGB values
 */
// TODO: make direct call, no array! simplify
function getRandomColors(num) {
    "use strict";
    try {
            var colors = [];
            for (var i = 0; i < num; i++) {
                var r = Math.round(Math.random() * 255);
                var g = Math.round(Math.random() * 255);
                var b = Math.round(Math.random() * 255);
                colors.push(`rgba(${r}, ${g}, ${b})`);
            }
            return colors;    
    } catch (error) {
        console.log(error.message);
        location.reload();
    }
    
}

/**
* Inits Chart instances from report-data
* @param {*} returnData report-data from emba
*/
function makeCharts(returnData) {
    "use strict";
    if (returnData.total_firmwares !== 0) {
      firmwareAnalysed.textContent = returnData.total_firmwares;
      accumulatedEntropy.setAttribute('value', returnData.entropy_value.mean);
      entropyMeterLabel.textContent = 'Average Entropy Value: ' + returnData.entropy_value.sum.toFixed(2);
      totalFiles.textContent = returnData.files.sum;
      totalDirectories.textContent = returnData.directories.sum;
      totalBinaries.textContent = returnData.bins_checked.sum;
      totalCve.textContent = returnData.cve_medium.sum + returnData.cve_low.sum + returnData.cve_high.sum;
      totalIssues.textContent = returnData.exploits.sum;
    } else {
      firmwareAnalysed.textContent = "no data";
      accumulatedEntropy.setAttribute('value', 0);
      entropyMeterLabel.textContent = 'Average Entropy Value: no data';
      totalFiles.textContent = "no data";
      totalDirectories.textContent = "no data";
      totalBinaries.textContent = "no data";
      totalCve.textContent = "no data";
      totalIssues.textContent = "no data";
    }

    let cvePieChart = new Chart(accumulatedCvePie, {
        type: 'pie',
        data: {
            labels: [
                'CVE-High',
                'CVE-Low',
                'CVE-Medium'
            ],
            datasets: [{
                label: 'CVE DATA',
                data: [returnData.cve_high.sum, returnData.cve_low.sum, returnData.cve_medium.sum],
                backgroundColor: [
                    'rgb(255, 99, 132)',
                    'rgb(54, 162, 235)',
                    'rgb(255, 205, 86)'
                ],
                hoverOffset: 4
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: 'CVE Data',
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        }
    });

    let nxpiechart = new Chart(nxpie, {
        type: 'pie',
        data: {
            labels: [
                'Binaries without NX',
                'Binaries with NX',
            ],
            datasets: [{
                labels: ['binaries with NX', 'binaries without NX'],
                data: [returnData.no_exec.sum, (returnData.bins_checked.sum - returnData.no_exec.sum)],
                backgroundColor: ['#493791', '#291771'],
            }, ],
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: 'No eXecute',
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        }
    });

    let piepiechart = new Chart(piepie, {
        type: 'pie',
        data: {
            labels: [
                'Not PIE binaries',
                'PIE enabled binaries',
            ],
            datasets: [{
                labels: ['binaries with PIE', 'binaries without PIE'],
                data: [returnData.pie.sum, (returnData.bins_checked.sum - returnData.pie.sum)],
                backgroundColor: ['#1b1534', '#000014'],
            }, ],
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: 'Position Independent (PIE)',
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        }
    });

    let relropiechart = new Chart(relropie, {
        type: 'pie',
        data: {
            labels: [
                'Binaries without RELRO',
                'Binaries with RELRO',
            ],
            datasets: [{
                labels: ['binaries with RELRO', 'binaries without RELRO'],
                data: [returnData.relro.sum, (returnData.bins_checked.sum - returnData.relro.sum)],
                backgroundColor: ['#7b919d', '#5b717d'],
            }, ],
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: 'RELRO',
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        }
    });

    let canarypiechart = new Chart(canarypie, {
        type: 'pie',
        data: {
            labels: [
                'Binaries without stack canaries',
                'Binaries with stack canaries',
            ],
            datasets: [{
                labels: ['binaries with CANARY', 'binaries without CANARY'],
                data: [returnData.canary.sum, (returnData.bins_checked.sum - returnData.canary.sum)],
                backgroundColor: ['#525d63', '#323d43'],
            }, ],
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: 'Stack canaries',
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        }
    });

    let strippedpiechart = new Chart(strippedpie, {
        type: 'pie',
        data: {
            labels: [
                'Stripped binaries',
                'Not stripped binaries',
            ],
            datasets: [{
                labels: ['binaries with Stripped', 'binaries without Stripped'],
                data: [returnData.stripped.sum, (returnData.bins_checked.sum - returnData.stripped.sum)],
                backgroundColor: ['#009999', '#005050'],
            }, ],
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: 'Debugging information',
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        }
    });

    var archLabels = Object.keys(returnData.architecture_verified);
    var archCounts = Object.values(returnData.architecture_verified);
    let architectureBarChart = new Chart(accumulatedArchitecture, {
        type: 'bar',
        data: {
            labels: archLabels,
            datasets: [{
                label: 'Architecture Distribution',
                labels: archLabels,
                data: archCounts,
                borderWidth: 1,
                backgroundColor: getRandomColors(archLabels.length)
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: 'Architecture Distribution',
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        }
    });

    var osLabels = Object.keys(returnData.os_verified);
    var osCounts = Object.values(returnData.os_verified);
    let osBarChart = new Chart(accumulatedOs, {
        type: 'bar',
        data: {
            labels: osLabels,
            datasets: [{
                label: 'OS Distribution',
                labels: osLabels,
                data: osCounts,
                borderWidth: 1,
                backgroundColor: getRandomColors(osLabels.length)
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: 'OS Distribution',
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        },
    });


    var topBinaryLabels = Object.keys(returnData.top_strcpy_bins);
    var topBinaryCounts = Object.values(returnData.top_strcpy_bins);
    let topBinaryBar = new Chart(topBinaryTypes, {
        type: 'bar',
        data: {
            labels: topBinaryLabels,
            datasets: [{
                label: 'Number of STRCPY used',
                labels: topBinaryLabels,
                data: topBinaryCounts,
                borderWidth: 1,
                backgroundColor: getRandomColors(topBinaryLabels.length)
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                title: {
                    display: true,
                    text: 'Binaries with weak functions',
                    position: 'top',
                    color: 666,
                    padding: {
                        top: 15,
                        bottom: 10
                    },
                    font: {
                        size: 24
                    }
                },
                legend: {
                    display: true,
                    position: 'bottom',
                    labels: {
                        fontColor: '#000'
                    }
                },
                layout: {
                    padding: {
                        left: 0,
                        right: 0,
                        bottom: 0,
                        top: 0
                    }
                },
                tooltips: {
                    enabled: true
                }
            }
        },

    });


    var topEntropyLabels = [];
    var topEntropyValues = [];

    for (var i = 0; i < returnData.top_entropies.length; i++) {
        topEntropyLabels.push(returnData.top_entropies[i].name);
        topEntropyValues.push(returnData.top_entropies[i].entropy_value);
    }

    let topEntropyBar = new Chart(topEntropies, {
            type: 'doughnut',
            data: {
                labels: topEntropyLabels,
                datasets: [{
                    label: 'Firmware entropie values',
                    labels: topEntropyLabels,
                    data: topEntropyValues,
                    borderWidth: 1,
                    backgroundColor: getRandomColors(topEntropyLabels.length)
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                plugins: {
                    title: {
                        display: true,
                        text: 'Firmware entropie values',
                        position: 'top',
                        color: 666,
                        padding: {
                            top: 15,
                            bottom: 10
                        },
                        font: {
                            size: 24
                        }
                    },
                    legend: {
                        display: true,
                        position: 'bottom',
                        labels: {
                            fontColor: '#000'
                        }
                    },
                    layout: {
                        padding: {
                            left: 0,
                            right: 0,
                            bottom: 0,
                            top: 0
                        }
                    },
                    tooltips: {
                        enabled: true
                    },
                },
            }
        }

    );

}

/**
 * Gets Accumulated data of all firmware scans analysed.
 * @returns Data for the Graphs
 */
function get_accumulated_reports() {
    "use strict";
    try {
        let url = window.location.origin + "/get_accumulated_reports/";
        return $.getJSON(url).then(function (data) {
        console.log(data);
        return data;
        });    
    } catch (error) {
        console.log(error.message);
        location.reload();
    }
    
}

/**
 * Develop Charts from the Analysed data .
 */
 get_accumulated_reports().then(makeCharts);

embark/uploader/templatetags/filters.py METASEP
from django import template
from django.forms.fields import CheckboxInput

register = template.Library()


@register.filter(name='is_checkbox')
def is_checkbox(value):
    return isinstance(value, CheckboxInput)

embark/uploader/templatetags/__init__.py METASEP

embark/uploader/management/__init__.py METASEP

embark/reporter/migrations/__init__.py METASEP

embark/embark/settings/dev.py METASEP
from pathlib import Path
import os

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent.parent
HASHID_FIELD_SALT = os.environ.get('HASHID_SALT')
HASHID_FIELD_MIN_LENGTH = os.environ.get('HASHID_FIELD_MIN_LENGTH', 7)
HASHID_FIELD_ENABLE_HASHID_OBJECT = os.environ.get('HASHID_FIELD_ENABLE_HASHID_OBJECT', False)
HASHID_FIELD_ENABLE_DESCRIPTOR = os.environ.get('HASHID_FIELD_ENABLE_DESCRIPTOR', False)
SECRET_KEY = os.environ.get('SECRET_KEY')
DEBUG = bool(os.environ.get('EMBARK_DEBUG', True))
ALLOWED_HOSTS = ['*']

EMBA_ROOT = '/app/emba/'
EMBA_LOG_ROOT = os.path.join(BASE_DIR.parent, 'emba_logs')
EMBA_LOG_URL = 'emba_logs/'

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'mod_wsgi.server',
    'django_apscheduler',
    'channels',
    'uploader',
    'users',
    'reporter',
    'dashboard'
]

AUTH_USER_MODEL = 'users.User'

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'embark.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates', EMBA_LOG_ROOT],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

CSRF_COOKIE_SAMESITE = 'Strict'
CSRF_COOKIE_HTTPONLY = False  # False since we will grab it via universal-cookies

SESSION_COOKIE_SAMESITE = 'Strict'
SESSION_COOKIE_HTTPONLY = True

WSGI_APPLICATION = 'embark.wsgi.application'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': os.environ.get('DATABASE_NAME'),
        'USER': os.environ.get('DATABASE_USER'),
        'PASSWORD': os.environ.get('DATABASE_PASSWORD'),
        'HOST': os.environ.get('DATABASE_HOST'),
        'PORT': os.environ.get('DATABASE_PORT'),
        'CONN_MAX_AGE': 300,
        'OPTIONS': {'charset': 'utf8mb4'},
    },
}

LOG_LEVEL = os.environ.get('DJANGO_LOG_LEVEL', 'DEBUG').upper()
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'console': {
            'format': '%(name)-12s %(levelname)-8s %(message)s',
            'datefmt': "%Y-%m-%d %H:%M:%S"
        },
        'file': {
            'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
            'datefmt': "%Y-%m-%d %H:%M:%S"
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'console'
        },
        'file': {
            'level': LOG_LEVEL,
            'class': 'logging.FileHandler',
            'formatter': 'file',
            'filename': str(BASE_DIR / 'embark.log'),
        }
    },
    'loggers': {
        'web': {
            'level': LOG_LEVEL,
            'handlers': ['file'],
            'class': 'logging.handlers.RotatingFileHandler',
            'maxBytes': 1024 * 1024 * 10,  # 10 MiB
            'backupCount': 2,
        },
    }
}

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = False


STATIC_URL = '/static/'
STATICFILES_DIRS = [
    BASE_DIR / 'static/'
]
# STATICFILES_STORAGE
# STATICFILES_FINDERS

# URL of Login-Page
LOGIN_URL = ''

# URL of Logout-Page
LOGOUT_REDIRECT_URL = ''

# Added for FIle storage to get the path to save Firmware images.
MEDIA_ROOT = os.path.join('/app/', 'uploadedFirmwareImages')  # media directory in the root directory
MEDIA_URL = '/uploadedFirmwareImages/'

# Active Firmware
ACTIVE_FW = '/app/uploadedFirmwareImages/active/'

REDIS_HOST = os.environ.get('REDIS_HOST', '127.0.0.1')
REDIS_PORT = int(os.environ.get('REDIS_PORT', 6379))

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
ASGI_APPLICATION = 'embark.asgi.application'

# Format string for displaying run time timestamps in the Django admin site. The default
# just adds seconds to the standard Django format, which is useful for displaying the timestamps
# for jobs that are scheduled to run on intervals of less than one minute.
#
# See https://docs.djangoproject.com/en/dev/ref/settings/#datetime-format for format string
# syntax details.
APSCHEDULER_DATETIME_FORMAT = "N j, Y, f:s a"

# Maximum run time allowed for jobs that are triggered manually via the Django admin site, which
# prevents admin site HTTP requests from timing out.
#
# Longer running jobs should probably be handed over to a background task processing library
# that supports multiple background worker processes instead (e.g. Dramatiq, Celery, Django-RQ,
# etc. See: https://djangopackages.org/grids/g/workers-queues-tasks/ for popular options).
APSCHEDULER_RUN_NOW_TIMEOUT = 25  # Seconds

# redis/channel layers setup
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [(REDIS_HOST, REDIS_PORT)],
        },
    },
}

embark/embark/settings/deploy.py METASEP
"""
Django settings for djangoProject project.
Generated by 'django-admin startproject' using Django 3.2.
"""
from pathlib import Path
import os

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent.parent

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

HASHID_FIELD_SALT = os.environ.get('HASHID_SALT')
HASHID_FIELD_MIN_LENGTH = os.environ.get('HASHID_FIELD_MIN_LENGTH', 7)
HASHID_FIELD_ENABLE_HASHID_OBJECT = os.environ.get('HASHID_FIELD_ENABLE_HASHID_OBJECT', False)
HASHID_FIELD_ENABLE_DESCRIPTOR = os.environ.get('HASHID_FIELD_ENABLE_DESCRIPTOR', False)
SECRET_KEY = os.environ.get('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = bool(os.environ.get('EMBARK_DEBUG', True))

ALLOWED_HOSTS = ['*']

# EMBA stuff
# EMBA location
EMBA_ROOT = '/app/emba/'
EMBA_LOG_ROOT = '/app/www/emba_logs'
EMBA_LOG_URL = 'emba_logs/'

# Application definition - defines what apps gets migrated
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_apscheduler',
    'mod_wsgi.server',
    'channels',
    'uploader',
    'users',
    'reporter',
    'dashboard'
]

AUTH_USER_MODEL = 'users.User'

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'embark.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates', EMBA_LOG_ROOT],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# CSRF and SESSION cookies are:
# 1. separate for now but basically linked (==CSRF_USE_SESSION)
# 2. only active after login

# CSRF_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'Strict'
CSRF_COOKIE_HTTPONLY = False  # False since we will grab it via universal-cookies

SESSION_COOKIE_SAMESITE = 'Strict'
# SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True

WSGI_APPLICATION = 'embark.wsgi.application'
ASGI_APPLICATION = 'embark.asgi.application'

# Database - uses *.env to configure
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': os.environ.get('DATABASE_NAME'),
        'USER': os.environ.get('DATABASE_USER'),
        'PASSWORD': os.environ.get("DATABASE_PASSWORD"),
        'HOST': os.environ.get('DATABASE_HOST'),
        'PORT': os.environ.get('DATABASE_PORT'),
        'CONN_MAX_AGE': 300,
        'OPTIONS': {'charset': 'utf8mb4'},
    },
}

LOG_LEVEL = os.environ.get('DJANGO_LOG_LEVEL', 'DEBUG').upper()
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'console': {
            'format': '%(name)-12s %(levelname)-8s %(message)s',
            'datefmt': "%Y-%m-%d %H:%M:%S"
        },
        'file': {
            'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
            'datefmt': "%Y-%m-%d %H:%M:%S"
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'console'
        },
        'file': {
            'level': LOG_LEVEL,
            'class': 'logging.FileHandler',
            'formatter': 'file',
            'filename': BASE_DIR / 'embark.log',
        }
    },
    'loggers': {
        'web': {
            'level': LOG_LEVEL,
            'handlers': ['file'],
            'class': 'logging.handlers.RotatingFileHandler',
            'maxBytes': 1024 * 1024 * 10,  # 10 MiB
            'backupCount': 2,
        },
    }
}

# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = False

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = '/app/www/static/'
STATICFILES_DIRS = [
    BASE_DIR / 'static/'
]
# STATICFILES_STORAGE
# STATICFILES_FINDERS

# URL of Login-Page
LOGIN_URL = ''

# URL of Logout-Page
LOGOUT_REDIRECT_URL = ''

# Upload Media
MEDIA_ROOT = '/app/www/media/'
MEDIA_URL = '/media/'

# Active Firmware
ACTIVE_FW = '/app/www/active/'

REDIS_HOST = os.environ.get('REDIS_HOST', '127.0.0.1')
REDIS_PORT = int(os.environ.get('REDIS_PORT', 6379))

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'


# Format string for displaying run time timestamps in the Django admin site. The default
# just adds seconds to the standard Django format, which is useful for displaying the timestamps
# for jobs that are scheduled to run on intervals of less than one minute.
#
# See https://docs.djangoproject.com/en/dev/ref/settings/#datetime-format for format string
# syntax details.
APSCHEDULER_DATETIME_FORMAT = "N j, Y, f:s a"

# Maximum run time allowed for jobs that are triggered manually via the Django admin site, which
# prevents admin site HTTP requests from timing out.
#
# Longer running jobs should probably be handed over to a background task processing library
# that supports multiple background worker processes instead (e.g. Dramatiq, Celery, Django-RQ,
# etc. See: https://djangopackages.org/grids/g/workers-queues-tasks/ for popular options).
APSCHEDULER_RUN_NOW_TIMEOUT = 25  # Seconds

# redis/channel layers setup
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [(REDIS_HOST, REDIS_PORT)],
        },
    },
}

# SSL stuff
SECURE_HSTS_SECONDS = 0
SECURE_SSL_REDIRECT = False

embark/dashboard/migrations/__init__.py METASEP

embark/templates/navigation.html METASEP
{% load static %}
{% block style %}<link rel="stylesheet" type="text/css" href="{% static 'content/css/navigation.css' %}"/>{% endblock style %}
{% block navigation %}
    <div id="navigation">
        <div class="logo-content">
            <div class="logo">
                <a href="{% url 'embark-login' %}" onclick="">
                    <img src="{% static 'content/images/embark_logo_text.png' %}" alt="EMBArk" height="auto" width="auto"/>
                </a>
            </div>
            <div class="nav-toggle" onclick="navToggle()">
                <svg id="menuBtn" height="48" viewBox="0 0 21 21" width="48" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#fff" stroke-linecap="round" stroke-linejoin="round"><path d="m4.5 6.5h12"/><path d="m4.498 10.5h11.997"/><path d="m4.5 14.5h11.995"/></g></svg>
            </div>
        </div>
        <ul>
            <li>
                <a href="{% url 'embark-MainDashboard' %}" onclick="">
                    <span class="icon">
                        <svg height="48px" viewBox="0 0 21 21" width="48px" xmlns="http://www.w3.org/2000/svg"><path d="m3.49859901.50058486-2 .00245141c-.55180573.00067635-.9987743.4481931-.9987743.99999924v1.99673096c0 .51283584.38604019.93550716.88337888.99327227l.11784682.00672698 2-.00245141c.55180573-.00067635.9987743-.4481931.9987743-.99999925v-1.9967302c0-.51283659-.38604019-.93550791-.88337887-.99327302zm6 0-2 .00245141c-.55180573.00067635-.9987743.4481931-.9987743.99999924v1.99673096c0 .51283584.38604019.93550716.88337888.99327227l.11784682.00672698 2-.00245141c.55180569-.00067635.99877429-.4481931.99877429-.99999925v-1.9967302c0-.51283659-.3860402-.93550791-.88337886-.99327302zm-6 6-2 .00245141c-.55180573.00067635-.9987743.4481931-.9987743.99999924v1.99673096c0 .51283583.38604019.93550713.88337888.99327223l.11784682.006727 2-.0024514c.55180573-.0006763.9987743-.4481931.9987743-.99999924v-1.9967302c0-.51283659-.38604019-.93550791-.88337887-.99327302zm6 0-2 .00245141c-.55180573.00067635-.9987743.4481931-.9987743.99999924v1.99673096c0 .51283583.38604019.93550713.88337888.99327223l.11784682.006727 2-.0024514c.55180569-.0006763.99877429-.4481931.99877429-.99999924v-1.9967302c0-.51283659-.3860402-.93550791-.88337886-.99327302z" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" transform="translate(5 5)"/></svg>
                    </span>
                    <span class="nav-link">Dashboard</span>
                </a>
            </li>

            <li class="submenu">
                <div class="dropdown">
                    <a data-toggle="collapse" aria-expanded="true">
                        <span class="icon" onclick="navToggle()">
                            <svg height="48px" viewBox="0 0 21 21" width="48px" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" transform="translate(4 3)"><path d="m2.5 4.753 4-4.232 4 4.191"/><path d="m6.5.5v11"/><path d="m.5 14.5h12"/></g></svg>
                        </span>
                        <span class="nav-link" >Uploader</span>
                    </a>
                    <ul class="dropdown-content">
                            <a href="{% url 'embark-uploader-home' %}" data-link>
                                <span class="nav-link">Upload and Start</span>
                            </a>
                            <a href="{% url 'embark-uploader-delete' %}" data-link>
                                <span class="nav-link">Delete Firmware</span>
                            </a>
                    </ul>
                 </div>
             </li>

            <li>
                <a href="{% url 'embark-ServiceDashboard' %}">
                    <span class="icon">
                        <svg height="48px" viewBox="0 0 21 21" width="48px" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" transform="matrix(-1 0 0 1 19 2)"><circle cx="8.5" cy="8.5" r="8"/><path d="m8.5 5.5v4h-3.5"/></g></svg>
                    </span>
                    <span class="nav-link">Progress</span>
                </a>
            </li>
            <li>
                <a href="{% url 'embark-ReportDashboard' %}">
                    <span class="icon">
                        <svg height="48px" viewBox="0 0 21 21" width="48px" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" transform="translate(4 3)"><path d="m3.5 1.5c-.42139382 0-1.08806048 0-2 0-.55228475 0-1 .44771525-1 1v11c0 .5522848.44771525 1 1 1h10c.5522847 0 1-.4477152 1-1v-11c0-.55228475-.4477153-1-1-1-.8888889 0-1.55555556 0-2 0"/><path d="m4.5.5h4c.55228475 0 1 .44771525 1 1s-.44771525 1-1 1h-4c-.55228475 0-1-.44771525-1-1s.44771525-1 1-1z"/><path d="m5.5 5.5h5"/><path d="m5.5 8.5h5"/><path d="m5.5 11.5h5"/><path d="m2.5 5.5h1"/><path d="m2.5 8.5h1"/><path d="m2.5 11.5h1"/></g></svg>
                    </span>
                    <span class="nav-link">Reports</span>
                </a>
            </li>
            <li class="submenu">
               <div class="dropdown">
                   <a data-toggle="collapse" aria-expanded="true">
                       <span class="icon" onclick="navToggle()"><svg height="48px" viewBox="0 0 21 21" width="48px" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" transform="matrix(-1 0 0 1 19 2)"><path d="m7.5.5c1.65685425 0 3 1.34314575 3 3v2c0 1.65685425-1.34314575 3-3 3s-3-1.34314575-3-3v-2c0-1.65685425 1.34314575-3 3-3zm7 14v-.7281753c0-3.1864098-3.6862915-5.2718247-7-5.2718247s-7 2.0854149-7 5.2718247v.7281753c0 .5522847.44771525 1 1 1h12c.5522847 0 1-.4477153 1-1z"/></g></svg></span>
                    <span class="nav-link" >My Account</span>
                   </a>
                   <ul class="dropdown-content">
                        <a href="{% url 'embark-user-menu' %}">
                           <span class="nav-link">User Menu(unavailable)</span>
                        </a>
                        <a href="{% url 'embark-password-change' %}">
                            <span class="nav-link">Change password</span>
                        </a>
                        <a href="{% url 'embark-acc-delete' %}">
                            <span class="nav-link">Delete account</span>
                        </a>
                   </ul>
                </div>
            </li>
        </ul>
        <div class="profile-content">
            <div class="profile">
                <div class="profile-details">
                    <div class="user-image">
                        <svg height="48px" viewBox="0 0 21 21" width="48px" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" transform="translate(2 2)"><circle cx="8.5" cy="8.5" r="8"/><path d="m14.5 13.5c-.6615287-2.2735217-3.1995581-3.0251263-6-3.0251263-2.72749327 0-5.27073171.8688092-6 3.0251263"/><path d="m8.5 2.5c1.6568542 0 3 1.34314575 3 3v2c0 1.65685425-1.3431458 3-3 3-1.65685425 0-3-1.34314575-3-3v-2c0-1.65685425 1.34314575-3 3-3z"/></g></svg>
                    </div>
                    <div class="user-id">
                    {{ username }}
                    </div>
                </div>
                <a href="{% url 'embark-logout' %}">
                    <svg id="log-out" height="48px" viewBox="0 0 21 21" width="48px" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" transform="translate(4 3)"><path d="m2.5.5h8c1.1045695 0 2 .8954305 2 2v10c0 1.1045695-.8954305 2-2 2h-8c-1.1045695 0-2-.8954305-2-2v-10c0-1.1045695.8954305-2 2-2z" stroke="#fff" stroke-linecap="round" stroke-linejoin="round"/><path d="m10.2022469 14.5-3.64502265-1.9482581c-.65083781-.3478717-1.05722425-1.0258787-1.05722425-1.763852v-6.57577979c0-.73797327.40638644-1.41598036 1.05722425-1.76385201l3.64502265-1.9482581" stroke="#fff" stroke-linecap="round" stroke-linejoin="round"/><circle cx="7.5" cy="7.5" fill="#fff" r="1"/></g></svg>
                </a>
            </div>
        </div>
    </div>
{% endblock navigation %}

embark/templates/base.html METASEP
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <meta name="description" content="EMBArk - The firmware security scanning environment"/>
    <meta name="keywords" content="HTML, CSS, JavaScript"/>
    <meta name="author" content="EMBArk-team"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>

    <link rel="icon" type="image/png" href="{% static 'content/images/favicon.png' %}"/>

    <title>{% block title %}{% endblock title %}</title>

    <!-- jQuery-confirm style sheet -->
    <link rel="stylesheet" href="{% static 'external/css/confirm.css' %}"/>

    <!-- Bootstrap and jquery-confirm style sheet -->
    <link rel="stylesheet" href="{% static 'external/css/bootstrap.css' %}"/>

    <!--DataTables style sheets-->
    <link rel="stylesheet" type="text/css" href="{% static 'external/css/datatable.css' %}"/>

    <!-- Style sheets-->
    <link rel="stylesheet" type="text/css" href="{% static 'content/css/globalStyle.css' %}"/>
    {% block style %}{% endblock style %}

    <!-- jQuery script -->
    <script type="text/javascript" src="{% static 'external/scripts/jquery.js' %}"></script>

    <!-- jQuery confirm script -->
    <script type="text/javascript" src="{% static 'external/scripts/confirm.js' %}"></script>

    <!-- Bootstrap script -->
    <script type="text/javascript" src="{% static 'external/scripts/bootstrap.js' %}"></script>

    <!--DataTables script-->
    <script type="text/javascript" src="{% static 'external/scripts/datatable.js' %}"></script>

    <!-- Charts script -->
    <script type="text/javascript" src="{% static 'external/scripts/charts.js' %}"></script>

    <!-- local Javascript files-->
    <script type="text/javascript" src="{% static 'scripts/main.js' %}"></script>
    <script type="text/javascript" src="{% static 'scripts/alertBox.js' %}"></script>
    {% block js %}{% endblock js %}

</head>
<body>
    <div class="container-fluid">
        {% block navigation %}{% endblock navigation %}
        <!--Main container-->
        <div class="main">
            {% block maincontent %}{% endblock maincontent %}
        </div>
    </div>
    {% block inlinejs %}{% endblock inlinejs %}
</body>
</html>

embark/users/views.py METASEP
# pylint: disable=R1705
import logging
from datetime import datetime

from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods

from django.contrib.auth import authenticate, login, logout, get_user
from django.contrib import messages
from django.conf import settings

from .models import User

logger = logging.getLogger('web')


@csrf_exempt
@require_http_methods(["GET", "POST"])
def register(request):
    if request.method == "POST":
        logger.debug(request.POST)
        data = {k: v[0] for k, v in dict(request.POST).items()}
        logger.debug(data)
        try:
            body = {k: v[0] for k, v in dict(request.POST).items()}
            try:
                username = body['username']
                password = body['password']
                confirm_password = body['confirm_password']

                if password == confirm_password:
                    logger.debug('Passwords match. Creating user')
                    user = User.objects.create(username=username)
                    user.set_password(password)
                    user.save()
                    logger.debug('User created')
                else:
                    logger.debug('Passwords do not match')
                    return render(
                        request,
                        'user/register.html',
                        {'error_message': True, 'message': 'Passwords do not match.'}
                    )

                return render(
                    request,
                    'user/login.html',
                    {'success_message': True, 'message': 'Registration successful.'}
                )

            except KeyError:
                logger.exception('Missing keys from data- Username, password, password_confirm')
                return render(request, 'user/register.html', {'error_message': True, 'message': 'User data is invalid.'})
        except Exception as error:
            logger.exception('Wide exception in Signup: %s', error)
            return render(
                request,
                'user/register.html',
                {'error_message': True, 'message': 'Something went wrong when signing up the user.'}
            )
    return render(request, 'user/register.html')


@require_http_methods(["GET", "POST"])
def embark_login(request):
    if request.method == "POST":
        logger.debug(request.POST)
        data = {k: v[0] for k, v in dict(request.POST).items()}
        logger.debug(data)
        try:
            body = {k: v[0] for k, v in dict(request.POST).items()}
            try:
                username = body['username']
                password = body['password']
            except KeyError:
                logger.exception('Missing keys from data- Username and password')
                return render(request, 'user/login.html', {'error_message': True, 'message': 'Username or password are wrong.'})

            logger.debug('Found user name and password')
            user = authenticate(request, username=username, password=password)
            if user is not None:
                logger.debug('User authenticated')
                login(request, user)
                logger.debug('User logged in')
                return HttpResponseRedirect('../../dashboard/main/')
            # else:
            logger.debug('User could not be authenticated')
            messages.info(request, "Invalid user data")
            return render(request, 'user/login.html', {'error_message': True, 'message': 'Invalid user data.'})
        except Exception as error:
            logger.exception('Wide exception in Signup: %s', error)
            messages.info(request, "Invalid user data")
            return render(request, 'user/login.html', {'error_message': True, 'message': 'Something went wrong when logging in the user.'})
    return render(request, 'user/login.html')


# @cache_control(no_cache=True, must_revalidate=True, no_store=True)
@login_required(login_url='/' + settings.LOGIN_URL)
def embark_logout(request):     # FIXME this just flushes session_id??!
    logout(request=request)
    logger.debug("Logout user %s", request)
    return render(request, 'user/login.html', {'success_message': True, 'message': 'Logout successful.'})


@login_required(login_url='/' + settings.LOGIN_URL)
@require_http_methods(["GET", "POST"])
def password_change(request):
    if request.method == "POST":
        logger.debug(request.POST)
        user = get_user(request)

        data = {k: v[0] for k, v in dict(request.POST).items()}
        logger.debug(data)
        try:
            body = {k: v[0] for k, v in dict(request.POST).items()}
            try:
                old_password = body['oldPassword']
                new_password = body['newPassword']
                confirm_password = body['confirmPassword']

                if user.check_password(old_password):
                    if old_password == new_password:
                        logger.debug('New password = old password')
                        return render(request, 'user/passwordChange.html', {'error_message': True, 'message': 'New password matches the old password'})
                    if new_password == confirm_password:
                        user.set_password(new_password)
                        user.save()
                        authenticate(request, username=user.username, password=new_password)
                        login(request, user)
                        logger.debug('New password set, user authenticated')
                        return render(request, 'user/passwordChangeDone.html', {'success_message': True, 'message': 'Password change successful.'})
                    else:
                        logger.debug('Passwords do not match')
                        return render(request, 'user/passwordChange.html', {'error_message': True, 'message': 'Passwords do not match.'})
                else:
                    logger.debug('Old password is incorrect')
                    return render(request, 'user/passwordChange.html', {'error_message': True, 'message': 'Old password is incorrect.'})
            except KeyError:
                logger.exception('Missing keys from data-passwords')
                return render(request, 'user/passwordChange.html', {'error_message': True, 'message': 'Some fields are empty!'})
        except Exception as error:
            logger.exception('Wide exception in Password Change: %s', error)
            return render(request, 'user/passwordChange.html', {'error_message': True, 'message': 'Something went wrong when changing the password for the user.'})
    return render(request, 'user/passwordChange.html')


@login_required(login_url='/' + settings.LOGIN_URL)
@require_http_methods(["GET"])
def menu(request):
    return render(request, 'user/menu.html')


@csrf_exempt
@login_required(login_url='/' + settings.LOGIN_URL)
@require_http_methods(["GET", "POST"])
def acc_delete(request):
    if request.method == "POST":
        logger.debug('disabling account')
        user = get_user(request)
        logger.debug(' %s Account: %s disabled', datetime.now().strftime("%H:%M:%S"), user)
        user.username = user.get_username() + '_disactivated_' + datetime.now().strftime(
            "%H:%M:%S")  # workaround for not duplicating entry users_user.username
        user.is_active = False
        user.save()
        return render(request, 'user/register.html', {'success_message': True, 'message': 'Account successfully deleted.'})
    return render(request, 'user/accountDelete.html')


@require_http_methods(["GET"])
@login_required(login_url='/' + settings.LOGIN_URL)
def get_log(request, log_type, lines):      # FIXME update or remove
    """
    View takes a get request with following params:
    1. log_type: selector of log file (daphne, migration, mysql_db, redis_db, uwsgi, web)
    2. lines: lines in log file
    Args:
        request: HTTPRequest instance

    Returns:

    """
    log_file_list = ["daphne", "migration", "mysql_db", "redis_db", "uwsgi", "web"]
    log_file = log_file_list[int(log_type)]
    file_path = f"{settings.BASE_DIR}/logs/{log_file}.log"
    logger.info('Load log file: %s', file_path)
    try:
        with open(file_path, encoding='utf-8') as file_:
            try:
                buffer_ = 500
                lines_found = []
                block_counter = -1

                while len(lines_found) <= lines:
                    try:
                        file_.seek(block_counter * buffer_, 2)
                    except IOError:
                        file_.seek(0)
                        lines_found = file_.readlines()
                        break

                    lines_found = file_.readlines()
                    block_counter -= 1

                result = lines_found[-(lines + 1):]
            except Exception as error:
                logger.exception('Wide exception in logstreamer: %s', error)

        return render(request, 'user/log.html', {'header': log_file + '.log', 'log': ''.join(result), 'username': request.user.username})
    except IOError:
        return render(request, 'user/log.html', {'header': 'Error', 'log': file_path + ' not found!', 'username': request.user.username})

embark/users/urls.py METASEP
from django.conf import settings
from django.urls import path

from users import views


urlpatterns = [
    path(settings.LOGIN_URL, views.embark_login, name='embark-login'),
    path('user/', views.menu, name='embark-user-menu'),
    path('register/', views.register, name='embark-register'),
    path('logout/', views.embark_logout, name='embark-logout'),
    # TODO account menu path('my-account/', views., name='embark-), for admin options etc
    path('my-account/password_change/', views.password_change, name='embark-password-change'),
    path('my-account/acc_delete/', views.acc_delete, name='embark-acc-delete'),
    path('log/<int:log_type>/<int:lines>/', views.get_log, name='log'),
]

embark/users/tests.py METASEP
from http import HTTPStatus
from django.http import HttpResponseRedirect

from django.test import TestCase
from django.test import Client

from users.models import User

# FIXME these are not up-to-date


class TestUsers(TestCase):
    def __init__(self):
        super().__init__(self)
        user = User.objects.create(username='testuser')
        user.set_password('12345')
        user.save()
        self.client = Client()

    def test_signup(self):
        """
        Right signup would redirect to home.
        Returns:

        """
        response = self.client.post('/signup', {'username': 'testuser1', 'password': '12345', 'confirm_password': '12345'})
        self.assertEqual(response.status_code, HTTPStatus.OK)

    def test_signin(self):
        """
        Right password would redirect to home.
        Returns: redirect (302) to mainDashboard

        """
        response = self.client.post('/signin', {'username': 'testuser1', 'password': '12345'})
        self.assertEqual(response.status_code, HttpResponseRedirect)

    def test_signin_wrong_password(self):
        """
        Wrongcd  password would render the same page again with 200 status code.
        Returns:

        """
        response = self.client.post('/signin', {'username': 'testuser', 'password': '1234'})
        self.assertEqual(response.status_code, HTTPStatus.OK)

    def test_signin_wrong_body(self):
        """
        Signin api expects two keys in the body.
        email and password. On getting wrong body.
        it would render the same page again with 200 status code.
        Returns:

        """
        response = self.client.post('/signin', {'email': 'testuser', 'password': '12345'})
        self.assertEqual(response.status_code, HTTPStatus.OK)

embark/users/models.py METASEP
import enum

from django.db import models
from django.contrib.auth.models import AbstractUser

from lib.choice_enum import ChoiceIntEnum


@enum.unique
class Role(ChoiceIntEnum):
    VIEWER = 0
    EDITOR = 1
    OWNER = 2
    MANAGER = 3


class Team(models.Model):
    name = models.CharField(max_length=150, unique=True, help_text='Name of the team')
    is_active = models.BooleanField(default=True, help_text='Whether this Team is active or not')
    created = models.DateTimeField(auto_now_add=True, help_text='Date time when this entry was created')
    modified = models.DateTimeField(auto_now=True, help_text='Date time when this entry was modified')


class User(AbstractUser):
    is_active = models.BooleanField(default=True, help_text='User active or not')
    created = models.DateTimeField(auto_now_add=True, help_text='Date time when this entry was created')
    modified = models.DateTimeField(auto_now=True, help_text='Date time when this entry was modified')


class TeamMember(models.Model):
    team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='team_member')
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_team_member')
    role = models.IntegerField(choices=Role.choices(), default=Role.VIEWER)
    is_active = models.BooleanField(default=True, help_text='Whether this team member is active or not')
    created = models.DateTimeField(auto_now_add=True, help_text='Date time when this entry was created')
    modified = models.DateTimeField(auto_now=True, help_text='Date time when this entry was modified')

embark/users/apps.py METASEP
from django.apps import AppConfig


class UsersConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'users'

embark/users/admin.py METASEP
from django.contrib import admin

from .models import User, Team, TeamMember

admin.site.register(User)
admin.site.register(Team)
admin.site.register(TeamMember)

embark/users/__init__.py METASEP

embark/uploader/views.py METASEP
# pylint: disable=W0613,C0206
import logging

from django.conf import settings
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, HttpResponseServerError
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods

from uploader.boundedexecutor import BoundedExecutor
from uploader.forms import FirmwareAnalysisForm, DeleteFirmwareForm
from uploader.models import FirmwareFile

logger = logging.getLogger('web')


@login_required(login_url='/' + settings.LOGIN_URL)
@require_http_methods(["GET"])
def uploader_home(request):
    if FirmwareFile.objects.all().count() > 0:
        analysis_form = FirmwareAnalysisForm(initial={'firmware': FirmwareFile.objects.latest('upload_date')})
        return render(request, 'uploader/fileUpload.html', {'success_message': False, 'analysis_form': analysis_form})
    return render(request, 'uploader/fileUpload.html')


@require_http_methods(["POST"])
@login_required(login_url='/' + settings.LOGIN_URL)
def save_file(request):
    """
    file saving on POST requests with attached file

    :params request: HTTP request

    :return: HttpResponse including the status
    """
    logger.info("User %s tryied to upload %s", request.user.username, request.FILES.getlist('file'))
    for file in request.FILES.getlist('file'):      # FIXME determin usecase for multi-file-upload in one request
        firmware_file = FirmwareFile.objects.create(file=file)
        firmware_file.save()

    return HttpResponse("successful upload")


@login_required(login_url='/' + settings.LOGIN_URL)
@require_http_methods(["GET", "POST"])
def start_analysis(request):
    """
    View to submit form for flags to run emba with
    if: form is valid
        checks if queue is not full
            starts emba process redirects to uploader page
        else: return Queue full
    else: returns Invalid form error
    Args:
        request: the http req with FirmwareAnalysisForm
    Returns: redirect

    """
    if request.method == 'POST':
        form = FirmwareAnalysisForm(request.POST)
        if form.is_valid():
            logger.debug("Posted Form is valid")
            logger.info("Starting analysis with %s", form.Meta.model.id)

            new_analysis = form.save(commit=False)
            new_analysis.user = request.user
            logger.debug(" FILE_NAME is %s", new_analysis.firmware.file.name)
            new_analysis.firmware_name = new_analysis.firmware.file.name
            new_analysis = form.save()

            # get the id of the firmware-file to submit
            new_firmware_file = FirmwareFile.objects.get(id=new_analysis.firmware.id)
            logger.info("Firmware file: %s", new_firmware_file)

            # inject into bounded Executor
            if BoundedExecutor.submit_firmware(firmware_flags=new_analysis, firmware_file=new_firmware_file):
                return HttpResponseRedirect("/dashboard/service/")
            logger.error("Server Queue full, or other boundenexec error")
            return HttpResponseServerError("Queue full")

    if FirmwareFile.objects.all().count() > 0:
        analysis_form = FirmwareAnalysisForm(initial={'firmware': FirmwareFile.objects.latest('upload_date')})
        return render(request, 'uploader/fileUpload.html', {'success_message': True, 'message': "Successfull upload", 'analysis_form': analysis_form})
    return render(request, 'uploader/fileUpload.html', {'success_message': True, 'message': "Please Upload a File first"})


@require_http_methods(["GET", "POST"])
@login_required(login_url='/' + settings.LOGIN_URL)
def delete_fw_file(request):
    """
    file deletion on POST requests with attached firmware file

    :params request: HTTP request

    :return: HttpResponse including the status
    """
    if request.method == 'POST':
        form = DeleteFirmwareForm(request.POST)

        if form.is_valid():
            logger.debug("Form %s is valid", form)

            # get relevant data
            firmware_file = form.cleaned_data['firmware']
            # if firmware_file.user is request.user:
            firmware_file.delete()
            return render(request, 'uploader/firmwareDelete.html', {'success_message': True, 'message': "Successfull delete"})

        logger.error("Form %s is invalid", form)
        logger.error("Form error: %s", form.errors)
        return HttpResponseBadRequest("invalid Form")
    if FirmwareFile.objects.all().count() > 0:
        form = DeleteFirmwareForm(initial={'firmware': FirmwareFile.objects.latest('upload_date').id})
        return render(request, 'uploader/firmwareDelete.html', {'form': form})
    return HttpResponseRedirect('/uploader/')

embark/uploader/urls.py METASEP
from django.urls import path

from uploader import views

# view routing
urlpatterns = [

    path('uploader/', views.uploader_home, name='embark-uploader-home'),
    path('uploader/save/', views.save_file, name='embark-uploader-save'),
    path('uploader/delete/', views.delete_fw_file, name='embark-uploader-delete'),
    path('uploader/start/', views.start_analysis, name='embark-uploader-start-analysis'),
]

embark/uploader/tests.py METASEP
from django.test import TestCase

from .models import FirmwareAnalysis, FirmwareFile


class TestModels(TestCase):

    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)
        self.fw_file = FirmwareFile.objects.create()
        self.fw_file.save()

    # TODO: add timeout
    def test_get_flags_all_true(self):
        """
        test get_flags() if all flags set to String/True
        """

        firmware = FirmwareAnalysis(firmware=self.fw_file)

        firmware.version = "version"
        firmware.vendor = "vendor"
        firmware.device = "device"
        firmware.notes = "notes"
        firmware.firmware_Architecture = "x64"
        firmware.cwe_checker = True
        firmware.dev_mode = True
        firmware.deep_extraction = True
        firmware.log_path = True
        firmware.grep_able_log = True
        firmware.relative_paths = True
        firmware.ANSI_color = True
        firmware.web_reporter = True
        firmware.emulation_test = True
        firmware.dependency_check = True
        firmware.multi_threaded = True
        firmware.firmware_remove = True

        expected_string = " -X version -Y vendor -Z device -N notes -a x64 -c -D -x -i -g -s -z -W -E -F -t -r"
        self.assertEqual(firmware.get_flags(), expected_string)

    def test_get_flags_all_false(self):
        """
        test get_flags() if all flags set to None/False
        """

        firmware = FirmwareAnalysis(firmware=self.fw_file)

        firmware.version = None
        firmware.vendor = None
        firmware.device = None
        firmware.notes = None
        firmware.firmware_Architecture = None
        firmware.cwe_checker = False
        firmware.dev_mode = False
        firmware.deep_extraction = False
        firmware.log_path = False
        firmware.grep_able_log = False
        firmware.relative_paths = False
        firmware.ANSI_color = False
        firmware.web_reporter = False
        firmware.emulation_test = False
        firmware.dependency_check = False
        firmware.multi_threaded = False
        firmware.firmware_remove = False

        expected_string = ""
        self.assertEqual(firmware.get_flags(), expected_string)

embark/uploader/test_boundedexecutor.py METASEP
from django.test import TestCase

from .boundedexecutor import BoundedExecutor, MAX_WORKERS, MAX_QUEUE


class TestBoundedExecutor(TestCase):

    def setUp(self):
        pass

    # TODO: add timeout
    def test_non_blocking_overflow(self):

        fut_list = []

        for _ in range(MAX_WORKERS + MAX_QUEUE):
            # if testing under windows use "timeout /T 5" instead of "sleep 5"
            fut = BoundedExecutor.submit(BoundedExecutor.run_emba_cmd, "sleep 5")
            self.assertIsNotNone(fut)
            fut_list.append(fut)

        for _ in range(MAX_WORKERS):
            fut = BoundedExecutor.submit(BoundedExecutor.run_emba_cmd, "sleep 5")
            self.assertIsNone(fut)

        for fut in fut_list:
            fut.result()

embark/uploader/models.py METASEP
from datetime import timedelta
import logging
import os
import shutil
import uuid
import re

from django.conf import settings
from django.db import models
from django import forms
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.utils.datetime_safe import datetime

# from hashid_field import HashidAutoField

from users.models import User as Userclass


logger = logging.getLogger('web')


class BooleanFieldExpertModeForm(forms.BooleanField):
    """
    class BooleanFieldExpertModeForm
    Extension of forms.BooleanField to support expert_mode and readonly option for BooleanFields in Forms
    """
    def __init__(self, *args, **kwargs):
        self.expert_mode = kwargs.pop('expert_mode', True)
        self.readonly = kwargs.pop('readonly', False)
        # super(BooleanFieldExpertModeForm, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)


class BooleanFieldExpertMode(models.BooleanField):
    """
    class BooleanFieldExpertModeForm
    Extension of models.BooleanField to support expert_mode and readonly for BooleanFields option for Models
    """
    def __init__(self, *args, **kwargs):
        self.expert_mode = kwargs.pop('expert_mode', True)
        self.readonly = kwargs.pop('readonly', False)
        # super(BooleanFieldExpertMode, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)

    def formfield(self, **kwargs):
        defaults = {'form_class': BooleanFieldExpertModeForm, 'expert_mode': self.expert_mode, 'readonly': self.readonly}
        defaults.update(kwargs)
        return models.Field.formfield(self, **defaults)


class CharFieldExpertModeForm(forms.CharField):
    """
    class BooleanFieldExpertModeForm
    Extension of forms.CharField to support expert_mode and readonly for CharField option for Forms
    """
    def __init__(self, *args, **kwargs):
        self.expert_mode = kwargs.pop('expert_mode', True)
        self.readonly = kwargs.pop('readonly', False)
        # super(CharFieldExpertModeForm, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)


class TypedChoiceFieldExpertModeForm(forms.TypedChoiceField):
    """
    class BooleanFieldExpertModeForm
    Extension of forms.TypedChoiceField to support expert_mode and readonly for TypedChoiceField option for Forms
    """
    def __init__(self, *args, **kwargs):
        self.expert_mode = kwargs.pop('expert_mode', True)
        self.readonly = kwargs.pop('readonly', False)
        # super(TypedChoiceFieldExpertModeForm, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)


class CharFieldExpertMode(models.CharField):
    """
    class CharFieldExpertMode
    Extension of models.BooleanField to support expert_mode and readonly for CharField option for Models
    """
    def __init__(self, *args, **kwargs):
        self.expert_mode = kwargs.pop('expert_mode', True)
        self.readonly = kwargs.pop('readonly', False)
        # super(CharFieldExpertMode, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)

    def formfield(self, **kwargs):
        defaults = {'form_class': CharFieldExpertModeForm, 'choices_form_class': TypedChoiceFieldExpertModeForm, 'expert_mode': self.expert_mode, 'readonly': self.readonly}
        defaults.update(kwargs)
        return models.Field.formfield(self, **defaults)


class FirmwareFile(models.Model):
    """
    class FirmwareFile
    Model to store zipped or bin firmware file and upload date
    """
    MAX_LENGTH = 127

    # id = HashidAutoField(primary_key=True, prefix='fw_')
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)

    is_archive = models.BooleanField(default=False, blank=True)
    upload_date = models.DateTimeField(default=datetime.now, blank=True)
    user = models.ForeignKey(Userclass, on_delete=models.SET_NULL, related_name='Fw_Upload_User', null=True, blank=True)

    def get_storage_path(self, filename):
        # file will be uploaded to MEDIA_ROOT/<id>/<filename>
        return os.path.join(f"{self.pk}", filename)

    file = models.FileField(upload_to=get_storage_path)

    def get_abs_path(self):
        return f"{settings.MEDIA_ROOT}/{self.pk}/{self.file.name}"

    def get_abs_folder_path(self):
        return f"{settings.MEDIA_ROOT}/{self.pk}"

    # def __init__(self, *args, **kwargs):
    #    super().__init__(*args, **kwargs)
        # self.file_name = self.file.name

    def __str__(self):
        return f"{self.file.name.replace('/', ' - ')}"  # this the only sanitizing we do?


@receiver(pre_delete, sender=FirmwareFile)
def delete_fw_pre_delete_post(sender, instance, **kwargs):
    """
    callback function
    delete the firmwarefile and folder structure in storage on recieve
    """
    if sender.file:
        shutil.rmtree(instance.get_abs_folder_path(), ignore_errors=False, onerror=logger.error("Error when trying to delete %s", instance.get_abs_folder_path()))
    else:
        logger.error("No related FW found for delete request: %s", str(sender))


class FirmwareAnalysis(models.Model):
    """
    class Firmware
    Model of firmware to be analyzed, basic/expert emba flags and metadata on the analyze process
    (1 FirmwareFile --> n FirmwareAnalysis)
    """
    MAX_LENGTH = 127

    # pk
    # id = HashidAutoField(primary_key=True, prefix='fwA_')
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
    # user
    user = models.ForeignKey(Userclass, on_delete=models.SET_NULL, related_name='Fw_Analysis_User', null=True)
    # pid from within boundedexec
    pid = models.BigIntegerField(help_text='process id of subproc', verbose_name='PID', blank=True, null=True)

    firmware = models.ForeignKey(FirmwareFile, on_delete=models.SET_NULL, help_text='', null=True, editable=True)
    firmware_name = models.CharField(editable=True, default="File unknown", max_length=MAX_LENGTH)

    # emba basic flags  FIXME change to int
    version = CharFieldExpertMode(
        help_text='Firmware version (only integers)', verbose_name="Firmware version", max_length=MAX_LENGTH,
        blank=True, expert_mode=False)
    vendor = CharFieldExpertMode(
        help_text='Firmware vendor (double quote your input)', verbose_name="Firmware vendor", max_length=MAX_LENGTH,
        blank=True, expert_mode=False)
    device = CharFieldExpertMode(
        help_text='Device (double quote your input)', verbose_name="Device", max_length=MAX_LENGTH, blank=True,
        expert_mode=False)
    notes = CharFieldExpertMode(
        help_text='Testing notes (double quote your input)', verbose_name="Testing notes", max_length=MAX_LENGTH,
        blank=True, expert_mode=False)

    # emba expert flags
    firmware_Architecture = CharFieldExpertMode(
        choices=[(None, 'Select architecture'), ('MIPS', 'MIPS'), ('ARM', 'ARM'), ('x86', 'x86'), ('x64', 'x64'), ('PPC', 'PPC')],
        verbose_name="Select architecture of the linux firmware",
        help_text='Architecture of the linux firmware [MIPS, ARM, x86, x64, PPC] -a will be added',
        max_length=MAX_LENGTH, blank=True, expert_mode=True)
    cwe_checker = BooleanFieldExpertMode(
        help_text='Enables cwe-checker,-c will be added', default=False, expert_mode=True, blank=True)
    dev_mode = BooleanFieldExpertMode(
        help_text='Run emba in developer mode, -D will be added ', default=False, expert_mode=True, blank=True)
    deep_extraction = BooleanFieldExpertMode(
        help_text='Enable deep extraction, -x will be added', default=False, expert_mode=True, blank=True)
    log_path = BooleanFieldExpertMode(
        help_text='Ignores log path check, -i will be added', default=False, expert_mode=True, blank=True)
    grep_able_log = BooleanFieldExpertMode(
        help_text='Create grep-able log file in [log_path]/fw_grep.log, -g will be added', default=True,
        expert_mode=True, blank=True)
    relative_paths = BooleanFieldExpertMode(
        help_text='Prints only relative paths, -s will be added', default=True, expert_mode=True, blank=True)
    ANSI_color = BooleanFieldExpertMode(
        help_text='Adds ANSI color codes to log, -z will be added', default=True, expert_mode=True, blank=True)
    web_reporter = BooleanFieldExpertMode(
        help_text='Activates web report creation in log path, -W will be added', default=True, expert_mode=True,
        blank=True)
    emulation_test = BooleanFieldExpertMode(
        help_text='Enables automated qemu emulation tests, -E will be added', default=True, expert_mode=True,
        blank=True)
    dependency_check = BooleanFieldExpertMode(
        help_text=' Checks dependencies but ignore errors, -F will be added', default=True, expert_mode=True,
        blank=True)
    multi_threaded = BooleanFieldExpertMode(
        help_text='Activate multi threading (destroys regular console output), -t will be added', default=True,
        expert_mode=True, blank=True)
    firmware_remove = BooleanFieldExpertMode(
        help_text='Remove extracted firmware file/directory after testint, -r will be added', default=True,
        expert_mode=True, blank=True)

    # embark meta data
    path_to_logs = models.FilePathField(default="/", blank=True)
    start_date = models.DateTimeField(default=datetime.now, blank=True)
    end_date = models.DateTimeField(default=datetime.min, blank=True)
    scan_time = models.DurationField(default=timedelta(), blank=True)
    duration = models.CharField(blank=True, null=True, max_length=100, help_text='')
    finished = models.BooleanField(default=False, blank=False)
    failed = models.BooleanField(default=True, blank=False)

    class Meta:
        app_label = 'uploader'

        """
        build shell command from input fields
        :params: None
        :return:
        """

    # def __init__(self, *args, **kwargs):
    #    super().__init__(*args, **kwargs)
    #    self.firmware_name = self.firmware.file.name

    def __str__(self):
        return f"{self.id}({self.firmware})"

    def get_flags(self):    # FIXME add all current options, DIRECT INPUT!! remove or sanitize
        """
        build shell command from input fields

        :return: string formatted input flags for emba
        """
        command = ""
        if self.version:
            command = command + " -X " + re.sub("[^0-9]", "", str(self.version))
        if self.vendor:
            command = command + " -Y " + re.sub("[^A-Za-z0–9]", "", str(self.vendor))
        if self.device:
            command = command + " -Z " + re.sub("[^A-Za-z0–9]", "", str(self.device))
        if self.notes:
            command = command + " -N " + re.sub("[^A-Za-z0–9]", "", str(self.notes))
        if self.firmware_Architecture:
            command = command + " -a " + str(self.firmware_Architecture)
        if self.cwe_checker:
            command = command + " -c"
        if self.dev_mode:
            command = command + " -D"
        if self.deep_extraction:
            command = command + " -x"
        if self.log_path:
            command = command + " -i"
        if self.grep_able_log:
            command = command + " -g"
        if self.relative_paths:
            command = command + " -s"
        if self.ANSI_color:
            command = command + " -z"
        if self.web_reporter:
            command = command + " -W"
        if self.emulation_test:
            command = command + " -E"
        if self.dependency_check:
            command = command + " -F"
        if self.multi_threaded:
            command = command + " -t"
        if self.firmware_remove:
            command = command + " -r"
        # running emba
        logger.info("final emba parameters %s", command)
        return command


class ResourceTimestamp(models.Model):
    """
    class ResourceTimestamp
    Model to store zipped or bin firmware file and upload date
    """

    timestamp = models.DateTimeField(default=datetime.now)
    cpu_percentage = models.FloatField(default=0.0)
    memory_percentage = models.FloatField(default=0.0)

embark/uploader/forms.py METASEP
# pylint: disable=E1101
import logging
import django
from django import forms

from uploader import models

logger = logging.getLogger('web')


class FirmwareAnalysisForm(forms.ModelForm):

    class Meta:
        model = models.FirmwareAnalysis

        fields = ('firmware', 'version', 'vendor', 'device', 'notes', 'firmware_Architecture', 'cwe_checker',
                  'dev_mode', 'deep_extraction', 'log_path', 'grep_able_log', 'relative_paths', 'ANSI_color',
                  'web_reporter', 'emulation_test', 'dependency_check', 'multi_threaded', 'firmware_remove')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for field in self.visible_fields():

            if isinstance(field.field.widget, django.forms.widgets.TextInput):
                field.field.widget.attrs['class'] = 'form-control formTxtField'
                field.field.widget.attrs['placeholder'] = field.label

            if isinstance(field.field.widget, django.forms.widgets.CheckboxInput):
                field.field.widget.attrs['class'] = 'form-check-input active'

            if isinstance(field.field.widget, django.forms.widgets.Select):
                field.field.widget.attrs['class'] = 'form-control select dropdownSelect'

            # TODO work to get rid of
            try:
                if field.field.readonly:
                    field.field.widget.attrs["disabled"] = "disabled"
            except Exception as error:
                logger.info("Exception passed: %s", error)
                # pass

            try:
                field.expert_mode = field.field.expert_mode
            except Exception as error:
                logger.info("Exception passed: %s", error)
                # pass

        self.base_fields['firmware'] = forms.ModelChoiceField(queryset=models.FirmwareFile.objects, empty_label='Select firmware')


class DeleteFirmwareForm(forms.Form):
    firmware = forms.ModelChoiceField(queryset=models.FirmwareFile.objects, empty_label='Select firmware-file to delete')

embark/uploader/boundedexecutor.py METASEP
# pylint: disable=R1732, C0201, E1129, W1509
import csv
import logging
import os
import shutil
from subprocess import Popen, PIPE
import re
import json

from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
from threading import BoundedSemaphore

from django.utils.datetime_safe import datetime
from django.conf import settings

from uploader.archiver import Archiver
from uploader.models import FirmwareAnalysis
from dashboard.models import Result
from embark.logreader import LogReader


logger = logging.getLogger('web')

# maximum concurrent running workers
MAX_WORKERS = 4
# maximum queue bound
MAX_QUEUE = MAX_WORKERS

# assign the threadpool max_worker_threads
executor = ThreadPoolExecutor(max_workers=MAX_WORKERS)
# create semaphore to track queue state
semaphore = BoundedSemaphore(MAX_QUEUE)

# emba directories
EMBA_SCRIPT_LOCATION = f"cd {settings.EMBA_ROOT} && sudo ./emba.sh"


class BoundedExecutor:
    """
    class BoundedExecutor
    This class is a wrapper of ExecuterThreadPool to enable a limited queue
    Used to handle concurrent emba analysis as well as emba.log analyzer
    """

    @classmethod
    def run_emba_cmd(cls, cmd, analysis_id=None, active_analyzer_dir=None):
        """
        run shell commands from python script as subprocess, waits for termination and evaluates returncode

        :param cmd: shell command to be executed
        :param id: primary key for firmware entry db identification
        :param active_analyzer_dir: active analyzer dir for deletion afterwards

        :return:
        """

        logger.info("Starting: %s", cmd)

        # get return code to evaluate: 0 = success, 1 = failure,
        # see emba.sh for further information
        exit_fail = False
        try:

            analysis = FirmwareAnalysis.objects.get(id=analysis_id)

            # The os.setsid() is passed in the argument preexec_fn so it's run after the fork() and before  exec() to run the shell.
            # attached but synchronous
            with open(f"{settings.EMBA_LOG_ROOT}/{analysis_id}/emba_run.log", "w+", encoding="utf-8") as file:
                proc = Popen(cmd, stdin=PIPE, stdout=file, stderr=file, shell=True, preexec_fn=os.setsid)   # nosec
                # Add proc to FirmwareAnalysis-Object
                analysis.pid = proc.pid
                analysis.save()
                logger.debug("subprocess got pid %s", proc.pid)
                # wait for completion
                proc.communicate()

            # success
            logger.info("Success: %s", cmd)

            # get csv log location
            csv_log_location = f"{settings.EMBA_LOG_ROOT}/{analysis_id}/emba_logs/f50_base_aggregator.csv"

            # read f50_aggregator and store it into a Result form
            logger.info('Reading report from: %s', csv_log_location)
            logger.debug("contents of that dir are %r", Path(csv_log_location).exists())
            # if Path(csv_log_location).exists:
            if Path(csv_log_location).is_file():
                cls.csv_read(analysis_id=analysis_id, path=csv_log_location, cmd=cmd)
            else:
                logger.error("CSV file %s for report: %s not generated", csv_log_location, analysis_id)
                logger.error("EMBA run was probably not successful!")
                exit_fail=True

            # take care of cleanup
            if active_analyzer_dir:
                shutil.rmtree(active_analyzer_dir)

        except Exception as execpt:
            # fail
            logger.error("EMBA run was probably not successful!")
            logger.error("run_emba_cmd error: %s", execpt)
            exit_fail = True
        finally:
            # finalize db entry
            if analysis_id:
                analysis.end_date = datetime.now()
                analysis.scan_time = datetime.now() - analysis.start_date
                analysis.duration = str(analysis.scan_time)
                analysis.finished = True
                analysis.failed = exit_fail
                analysis.save()

            logger.info("Successful cleaned up: %s", cmd)

    @classmethod
    def submit_firmware(cls, firmware_flags, firmware_file):
        """
        submit firmware + metadata for emba execution

        params firmware_flags: firmware model with flags and metadata
        params firmware_file: firmware file model to be analyzed

        return: emba process future on success, None on failure
        """
        active_analyzer_dir = f"{settings.ACTIVE_FW}{firmware_flags.id}/"
        logger.info("submitting firmware %s to emba", active_analyzer_dir)

        Archiver.copy(src=firmware_file.file.path, dst=active_analyzer_dir)

        # copy success
        emba_startfile = os.listdir(active_analyzer_dir)
        logger.debug("active dir contents %s", emba_startfile)
        if len(emba_startfile) == 1:
            image_file_location = f"{active_analyzer_dir}{emba_startfile.pop()}"
        else:
            logger.error("Uploaded file: %s doesnt comply with processable files.", firmware_file)
            logger.error("Zip folder with no extra directory in between.")
            shutil.rmtree(active_analyzer_dir)
            return None

        # get emba flags from command parser
        emba_flags = firmware_flags.get_flags()

        # evaluate meta information and safely create log dir

        emba_log_location = f"{settings.EMBA_LOG_ROOT}/{firmware_flags.id}/emba_logs"
        log_path = Path(emba_log_location).parent
        log_path.mkdir(parents=True, exist_ok=True)

        firmware_flags.path_to_logs = emba_log_location
        firmware_flags.save()

        # build command
        emba_cmd = f"{EMBA_SCRIPT_LOCATION} -f {image_file_location} -l {emba_log_location} {emba_flags}"

        # submit command to executor threadpool
        emba_fut = BoundedExecutor.submit(cls.run_emba_cmd, emba_cmd, firmware_flags.id, active_analyzer_dir)

        # start log_reader TODO: cancel future and return future
        # log_read_fut = BoundedExecutor.submit(LogReader, firmware_flags.pk)
        BoundedExecutor.submit(LogReader, firmware_flags.id)

        return emba_fut

    @classmethod
    def submit(cls, function_cmd, *args, **kwargs):
        """
        same as concurrent.futures.Executor#submit, but with queue

        params: see concurrent.futures.Executor#submit

        return: future on success, None on full queue
        """

        logger.info("submit fn: %s", function_cmd)
        logger.info("submit cls: %s", cls)

        # check if semaphore can be acquired, if not queue is full
        queue_not_full = semaphore.acquire(blocking=False)
        if not queue_not_full:
            logger.error("Executor queue full")
            return None
        try:
            future = executor.submit(function_cmd, *args, **kwargs)
        except Exception as error:
            logger.error("Executor task could not be submitted")
            semaphore.release()
            raise error
        else:
            future.add_done_callback(lambda x: semaphore.release())
            return future

    @classmethod
    def shutdown(cls, wait=True):
        """See concurrent.futures.Executor#shutdown"""

        executor.shutdown(wait)

    @classmethod
    def csv_read(cls, analysis_id, path, cmd):
        """
        This job reads the F50_aggregator file and stores its content into the Result model
        """

        with open(path, newline='\n', encoding='utf-8') as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=';')
            csv_list = []
            for row in csv_reader:
                csv_list.append(row)
                res_dict = {}
                for ele in csv_list:
                    if len(ele) == 2:
                        res_dict[ele[0]] = ele[1]
                    elif len(ele) == 3:
                        if not ele[0] in res_dict.keys():
                            res_dict[ele[0]] = {}
                        res_dict[ele[0]][ele[1]] = ele[2]
                    else:
                        pass

        logger.info("result dict: %s", res_dict)
        res_dict.pop('FW_path', None)

        entropy_value = res_dict.get("entropy_value", 0)
        # if type(entropy_value) is str:
        if isinstance(entropy_value, str):
            # entropy_value = re.findall(r'(\d+\.?\d*)', ' 7.55 bits per byte.')[0]
            entropy_value = re.findall(r'(\d+\.?\d*)', entropy_value)[0]
            entropy_value = entropy_value.strip('.')

        res = Result(
            firmware_analysis=FirmwareAnalysis.objects.get(id=analysis_id),
            emba_command=cmd.replace(f"cd {settings.EMBA_ROOT} && ", ""),
            architecture_verified=res_dict.get("architecture_verified", ''),
            # os_unverified=res_dict.get("os_unverified", ''),
            os_verified=res_dict.get("os_verified", ''),
            files=int(res_dict.get("files", 0)),
            directories=int(res_dict.get("directories", 0)),
            entropy_value=float(entropy_value),
            shell_scripts=int(res_dict.get("shell_scripts", 0)),
            shell_script_vulns=int(res_dict.get("shell_script_vulns", 0)),
            kernel_modules=int(res_dict.get("kernel_modules", 0)),
            kernel_modules_lic=int(res_dict.get("kernel_modules_lic", 0)),
            interesting_files=int(res_dict.get("interesting_files", 0)),
            post_files=int(res_dict.get("post_files", 0)),
            canary=int(res_dict.get("canary", 0)),
            canary_per=int(res_dict.get("canary_per", 0)),
            relro=int(res_dict.get("relro", 0)),
            relro_per=int(res_dict.get("relro_per", 0)),
            no_exec=int(res_dict.get("no_exec", 0)),
            no_exec_per=int(res_dict.get("no_exec_per", 0)),
            pie=int(res_dict.get("pie", 0)),
            pie_per=int(res_dict.get("pie_per", 0)),
            stripped=int(res_dict.get("stripped", 0)),
            stripped_per=int(res_dict.get("stripped_per", 0)),
            bins_checked=int(res_dict.get("bins_checked", 0)),
            strcpy=int(res_dict.get("strcpy", 0)),
            strcpy_bin=json.dumps(res_dict.get("strcpy_bin", {})),
            versions_identified=int(res_dict.get("versions_identified", 0)),
            cve_high=int(res_dict.get("cve_high", 0)),
            cve_medium=int(res_dict.get("cve_medium", 0)),
            cve_low=int(res_dict.get("cve_low", 0)),
            exploits=int(res_dict.get("exploits", 0)),
            metasploit_modules=int(res_dict.get("metasploit_modules", 0)),
            certificates=int(res_dict.get("certificates", 0)),
            certificates_outdated=int(res_dict.get("certificates_outdated", 0)),
        )
        res.save()
        return res

embark/uploader/archiver.py METASEP
import gzip
import logging
import os
from pathlib import Path
import re
import shutil

logger = logging.getLogger('web')


class Archiver:
    """
       Class unpacker
       This class use shutil function to unpack files
    """

    def __init__(self):
        # default formats: zip, tar, gztar, bztar, xztar

        # register additional formats ( gz )
        shutil.register_unpack_format('gz', ['.gz', ], self.gunzip_file)
        # pass

    @staticmethod
    def gunzip_file(file_name, work_dir):
        """
            special unzip function for .gz files

            :param file_name: file name of the gz file
            :param work_dir: directory where the archive is located

            :return:
        """

        # see warning about filename
        filename = os.path.split(file_name)[-1]
        filename = re.sub(r"\.gz$", "", filename, flags=re.IGNORECASE)

        # extract .gz file
        with gzip.open(file_name, 'rb') as f_in:
            with open(os.path.join(work_dir, filename), 'wb') as f_out:
                shutil.copyfileobj(f_in, f_out)

    @staticmethod
    def pack(base_name, archive_format, root_dir, base_dir):
        """
            pack function

            :param base_name: name of the file to create, excluding format-specific extension
            :param archive_format: archive format used to pack the directory
            :param root_dir: directory acting as root directory of the archive,
                             all paths in the archive will be relative to it
            :param base_dir: directory archiving is started from, relative to root_dir

            :return: name of archive on success
        """

        # TODO: check location
        return shutil.make_archive(base_name, archive_format, root_dir, base_dir)
        # alternative if single files be zipped:
        # with tarfile

    @staticmethod
    def unpack(file_location, extract_dir=None):
        """
            unpack function
            names non unique, since db takes care of that issue

            :param file_location: file location of the archive
            :param extract_dir: output directory where the archive contents are saved

            :return: True on success, Value error on wrong format, Exception otherwise
        """

        try:
            if extract_dir:
                shutil.unpack_archive(file_location, extract_dir)
            else:
                shutil.unpack_archive(file_location)
            logger.info("Unpacked file successful: %s", file_location)
            return True
        except shutil.ReadError as rerr:
            # logging.error(f"Format {file_location.split('.', 1)[1]} is not supported")
            logging.error("Format %s is not supported", file_location.split('.', 1)[1])
            raise ValueError from rerr
        except Exception as error:
            logging.error("Undefined Error during unpacking file: %s", file_location)
            logging.error(error)
            raise error

    @staticmethod
    def get_supported_formats():
        """
            lists all supported formats for unpacking

            :return: enumeration of supported formats as list of strings
        """

        return [name for (name, extensions, description) in shutil.get_unpack_formats()]

    @staticmethod
    def get_supported_extensions():
        """
            list all supported extensions for unpacking

            :return: enumeration of supported extensions as list of strings
        """

        extensions_list = [extensions for (name, extensions, description) in shutil.get_unpack_formats()]
        flat_list = [item for sublist in extensions_list for item in sublist]

        return flat_list

    @classmethod
    def check_extensions(cls, file_name):
        """
            checks file for extension integrity

            :param file_name: file to be checked

            :return: True if extension is supported, ValueError otherwise
        """

        for ext in cls.get_supported_extensions():
            if file_name.endswith(ext):
                return True

        logger.info("Format for %s is not supported by archiver", file_name)
        return False

    @classmethod
    def copy(cls, src, dst):
        """
        copy file from src to dst

            :param src: src file to be copied
            :param dst: location to be copied to

            :return: path on success
        """
        logger.debug("copping file %s to %s", src, dst)
        try:
            path = Path(dst)
            path.mkdir(parents=True, exist_ok=False)
            return shutil.copy(src, dst)
        except Exception as error:
            logger.error("Error copping firmware to active dir: %s", error)
        return None

embark/uploader/apps.py METASEP
from django.apps import AppConfig


class UploaderConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'uploader'

embark/uploader/admin.py METASEP
from django.contrib import admin

from .models import FirmwareAnalysis

admin.site.register(FirmwareAnalysis)

embark/uploader/__init__.py METASEP

embark/reporter/views.py METASEP
# pylint: disable=W0613,C0206

from pathlib import Path

import json
import os
import logging

from operator import itemgetter
from http import HTTPStatus
from uuid import UUID

from django.conf import settings
from django.forms import model_to_dict
from django.http.response import Http404
from django.template.loader import get_template
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.urls import reverse

from uploader.archiver import Archiver

from uploader.models import FirmwareAnalysis, ResourceTimestamp
from dashboard.models import Result

logger = logging.getLogger('web')


@require_http_methods(["GET"])
@login_required(login_url='/' + settings.LOGIN_URL)
def reports(request):
    html_body = get_template('uploader/reports.html')
    return HttpResponse(html_body.render({'username': request.user.username}))


@require_http_methods(["GET"])
@login_required(login_url='/' + settings.LOGIN_URL)
def html_report(request, analysis_id, html_file):
    report_path = Path(f'{settings.EMBA_LOG_ROOT}{request.path[10:]}')
    if FirmwareAnalysis.objects.filter(id=analysis_id).exists():
        analysis = FirmwareAnalysis.objects.get(id=analysis_id)
        if analysis.user == request.user:
            html_body = get_template(report_path)
            logger.info("html_report - analysis_id: %s html_file: %s", analysis_id, html_file)
            return HttpResponse(html_body.render({'embarkBackUrl': reverse('embark-ReportDashboard')}))
    logger.debug("could  not get template - %s", request)
    return HttpResponseBadRequest


@require_http_methods(["GET"])
@login_required(login_url='/' + settings.LOGIN_URL)
def html_report_path(request, analysis_id, html_path, html_file):
    report_path = Path(f'{settings.EMBA_LOG_ROOT}{request.path[10:]}')
    if FirmwareAnalysis.objects.filter(id=analysis_id).exists():
        analysis = FirmwareAnalysis.objects.get(id=analysis_id)
        if analysis.user == request.user:
            html_body = get_template(report_path)
            logger.info("html_report - analysis_id: %s path: %s html_file: %s", analysis_id, html_path, html_file)
            return HttpResponse(html_body.render({'embarkBackUrl': reverse('embark-ReportDashboard')}))
    logger.debug("could  not get path - %s", request)
    return HttpResponseBadRequest


@require_http_methods(["GET"])
@login_required(login_url='/' + settings.LOGIN_URL)
def html_report_download(request, analysis_id, html_path, download_file):    # Needed for EMBA?
    base_path = f"{settings.EMBA_LOG_ROOT}"
    if request.path.startswith('/'):
        file_path = request.path[1:]
    else:
        file_path = request.path[2:]
    full_path = os.path.normpath(os.path.join(base_path, file_path))
    if full_path.startswith(base_path):
        with open(full_path, 'rb') as requested_file:
            response = HttpResponse(requested_file.read(), content_type="text/plain")
            response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(full_path)
            logger.info("html_report - analysis_id: %s html_path: %s download_file: %s", analysis_id, html_path,
                        download_file)
            return response
    else:
        response = Http404
        return response


@require_http_methods(["GET"])
@login_required(login_url='/' + settings.LOGIN_URL)
def html_report_resource(request, analysis_id, img_file):
    content_type = "text/plain"

    if img_file.endswith(".css"):
        content_type = "text/css"
    elif img_file.endswith(".svg"):
        content_type = "image/svg+xml"
    elif img_file.endswith(".png"):
        content_type = "image/png"

    resource_path = Path(f'{settings.EMBA_LOG_ROOT}{request.path[10:]}')
    logger.info("html_report_resource - analysis_id: %s request.path: %s", analysis_id, request.path)

    try:
        # CodeQL issue is not relevant as the urls are defined via urls.py
        with open(resource_path, "rb") as file_:
            return HttpResponse(file_.read(), content_type=content_type)
    except IOError as error:
        logger.error(error)
        logger.error(request.path)

    # just in case -> back to report intro
    report_path = Path(f'{settings.EMBA_LOG_ROOT}{request.path[10:]}')
    html_body = get_template(report_path)
    return HttpResponse(html_body.render())


@require_http_methods(["GET"])
@login_required(login_url='/' + settings.LOGIN_URL)
def get_individual_report(request, analysis_id):
    """
    Get individual firmware report based on scan id (analysis_id)
    """
    if not analysis_id:
        logger.error('Bad request for get_individual_report')
        return JsonResponse(data={'error': 'Bad request'}, status=HTTPStatus.BAD_REQUEST)
    try:
        analysis_object = FirmwareAnalysis.objects.get(id=analysis_id)
        result = Result.objects.get(firmware_analysis=analysis_object)

        logger.debug("getting individual report for %s", result)

        return_dict = dict(model_to_dict(result), **model_to_dict(analysis_object))

        return_dict['name'] = analysis_object.firmware_name
        return_dict['strcpy_bin'] = json.loads(return_dict['strcpy_bin'])

        return JsonResponse(data=return_dict, status=HTTPStatus.OK)
    except Result.DoesNotExist:
        logger.error('Report for firmware_id: %s not found in database', analysis_id)
        return JsonResponse(data={'error': 'Not Found'}, status=HTTPStatus.NOT_FOUND)


@require_http_methods(["GET"])
# @login_required(login_url='/' + settings.LOGIN_URL)
def get_accumulated_reports(request):
    """
    Sends accumulated results for main dashboard
    Args:
        request:
    Returns:
        data = {
            'architecture_verified': {'arch_1': count, ....},
            'os_verified': {'os_1': count, .....},
            'all int fields in Result Model': {'sum': float/int, 'count': int, 'mean': float/int}
        }
    """
    results = Result.objects.all()
    top_5_entropies = results.order_by('-entropy_value')[:5]
    charfields = ['architecture_verified', 'os_verified']
    data = {}
    strcpy_bins = {}
    for result in results:
        result = model_to_dict(result)
        # Pop firmware object_id
        result.pop('firmware', None)
        result.pop('emba_command', None)

        # Get counts for all strcpy_bin values
        strcpy_bin = json.loads(result.pop('strcpy_bin', '{}'))
        for key in strcpy_bin:
            if key not in strcpy_bins:
                strcpy_bins[key] = 0
            strcpy_bins[key] += int(strcpy_bin[key])

        for charfield in charfields:
            if charfield not in data:
                data[charfield] = {}

            value = result.pop(charfield)
            if value not in data[charfield]:
                data[charfield][value] = 0

            data[charfield][value] += 1
        for field in result:
            if field not in data:
                data[field] = {'sum': 0, 'count': 0}
            data[field]['count'] += 1
            logger.debug("result-field %s", result[field])
            if result[field] is not None:
                if isinstance(result[field], UUID):
                    pass
                else:
                    data[field]['sum'] += result[field]

    for field in data:
        if field not in charfields:
            data[field]['mean'] = data[field]['sum'] / data[field]['count']
    data['total_firmwares'] = len(results)
    data['top_entropies'] = [{'name': r.firmware_analysis.firmware_name, 'entropy_value': r.entropy_value} for r in
                             top_5_entropies]

    # Taking top 10 most commonly occurring strcpy_bin values
    strcpy_bins = dict(sorted(strcpy_bins.items(), key=itemgetter(1), reverse=True)[:10])
    data['top_strcpy_bins'] = strcpy_bins

    return JsonResponse(data=data, status=HTTPStatus.OK)


@login_required(login_url='/' + settings.LOGIN_URL)
def download_zipped(request, analysis_id):
    """
    download zipped log directory

    :params request: HTTP request
    :params analysis_id: analyzed firmware id

    :return: HttpResponse with zipped log directory on success or HttpResponse including error message
    """

    try:
        firmware = FirmwareAnalysis.objects.get(id=analysis_id)

        if os.path.exists(firmware.path_to_logs):
            archive_path = Archiver.pack(firmware.path_to_logs, 'zip', firmware.path_to_logs, '.')
            logger.debug("Archive %s created", archive_path)
            with open(archive_path, 'rb') as requested_log_dir:
                response = HttpResponse(requested_log_dir.read(), content_type="application/zip")
                response['Content-Disposition'] = 'inline; filename=' + archive_path
                return response

        logger.warning("Firmware with ID: %s does not exist", analysis_id)
        return HttpResponse("Firmware with ID: %s does not exist", analysis_id)

    except FirmwareAnalysis.DoesNotExist as excpt:
        logger.warning("Firmware with ID: %s does not exist in DB", analysis_id)
        logger.warning("Exception: %s", excpt)
        return HttpResponse("Firmware ID does not exist in DB")
    except Exception as error:
        logger.error("Error occured while querying for Firmware object: %s", analysis_id)
        logger.error("Exception: %s", error)
        return HttpResponse("Error occured while querying for Firmware object")


@require_http_methods(["GET"])
# @login_required(login_url='/' + settings.LOGIN_URL)
def get_load(request):
    """
    JSON request of system-load
    """
    try:
        query_set = ResourceTimestamp.objects.all()
        result = {}
        # for k in model_to_dict(query_set[0]).keys():
        for modelnr in model_to_dict(query_set[0]):
            result[modelnr] = tuple(model_to_dict(querynr)[modelnr] for querynr in query_set)
        return JsonResponse(data=result, status=HTTPStatus.OK)
    except ResourceTimestamp.DoesNotExist:
        logger.error('ResourceTimestamps not found in database')
        return JsonResponse(data={'error': 'Not Found'}, status=HTTPStatus.NOT_FOUND)

embark/reporter/urls.py METASEP
from django.conf import settings
from django.urls import path

from . import views


# view routing
urlpatterns = [
    path(settings.EMBA_LOG_URL + '<uuid:analysis_id>/emba_logs/html-report/style/<str:img_file>', views.html_report_resource, name='embark-html-report-resource'),
    path(settings.EMBA_LOG_URL + '<uuid:analysis_id>/emba_logs/html-report/<str:html_file>', views.html_report, name='embark-html-report'),
    path(settings.EMBA_LOG_URL + '<uuid:analysis_id>/emba_logs/html-report/<str:html_path>/<str:html_file>', views.html_report_path, name='embark-html-report-path'),

    path('get_load/', views.get_load, name='embark-get-load'),
    path('get_individual_report/<uuid:analysis_id>/', views.get_individual_report, name='embark-get-individual-report'),
    path('get_accumulated_reports/', views.get_accumulated_reports, name='embark-get-accumulated-reports'),

    path(settings.EMBA_LOG_URL + '<uuid:analysis_id>/emba_logs/html-report/<path:html_path>/<str:download_file>/', views.html_report_download, name='embark-html-report-download'),
    path('download_zipped/<uuid:analysis_id>/', views.download_zipped, name='embark-download'),
]

embark/reporter/tests.py METASEP
from django.test import TestCase


class ReporterTestModel(TestCase):

    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)
        # TODO create EMBA-result with FWA and Result object

    def test_download(self):
        # TODO
        pass

embark/reporter/models.py METASEP
# from django.db import models

# Create your models here.

embark/reporter/apps.py METASEP
from django.apps import AppConfig


class ReporterConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'reporter'

embark/reporter/admin.py METASEP
# from django.contrib import admin

# Register your models here.

embark/reporter/__init__.py METASEP

embark/lib/choice_enum.py METASEP
import enum


class ChoiceIntEnum(enum.IntEnum):

    @classmethod
    def choices(cls):
        return [(x.value, x.name) for x in cls]

    @classmethod
    def has_value(cls, value):
        return any(value == item.value for item in cls)

embark/lib/__init__.py METASEP

embark/embark/wsgi.py METASEP
"""
WSGI config for djangoProject project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'embark.settings.deploy')

application = get_wsgi_application()

embark/embark/urls.py METASEP
"""djangoProject URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
# from django.contrib import admin
from django.urls import path, include
from django.contrib.staticfiles.urls import staticfiles_urlpatterns

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('', include('uploader.urls')),
    path('', include('users.urls')),
    path('', include('dashboard.urls')),
    path('', include('reporter.urls')),
] + staticfiles_urlpatterns()

embark/embark/test_logreader.py METASEP
import logging
from django.test import TestCase
from uploader.models import FirmwareAnalysis, FirmwareFile

from . import logreader

logger = logging.getLogger('web')


class TestLogreader(TestCase):

    def __init__(self):
        super().__init__(self)
        self.log_string1 = "[\x1b[0;33m*\x1b[0m] Tue Jun 15 12:12:26 UTC 2021 - P02_firmware_bin_file_check starting\n"
        self.log_string2 = "[\x1b[0;33m*\x1b[0m] Tue Jun 15 12:12:27 UTC 2021 - P02_firmware_bin_file_check finished\n"
        self.log_string3 = "[\x1b[0;35m!\x1b[0m]\x1b[0;35m Test ended on Sat Jul  3 00:07:10 UTC 2021 and took about 00:34:24\x1b[0m\n"
        self.log_string4 = "[*] Wed Apr 21 15:32:46 UTC 2021 - P05_firmware_bin_extractor starting"
        self.log_list = []
        self.log_list.append(self.log_string1)
        self.log_list.append(self.log_string2)
        self.log_list.append(self.log_string3)
        self.log_list.append(self.log_string4)

        # creat DB entry
        test_file = FirmwareFile.objects.create()
        firmware = FirmwareAnalysis(firmware=test_file)
        firmware.pk = -1
        firmware.save()

    def test_in_pro(self):
        logr = logreader.LogReader(-1)
        res1 = []
        res2 = []
        for line in self.log_list:
            res1, res2 = logr.produce_test_output(line)
        self.assertEqual("P02_firmware_bin_file_check", res1[0][0])
        self.assertEqual("Test ended on Sat Jul  3 00:07:10 UTC 2021 and took about 00:34:24",
                         res2[0][1])

embark/embark/routing.py METASEP
from django.urls import path
# from django.urls import re_path
# from django.conf.urls import url
from . import consumers

# url patterns for websocket communication -> equivalent to urls.py
ws_urlpatterns = [
    path('ws/progress/', consumers.WSConsumer.as_asgi()),
]

embark/embark/logreader.py METASEP
# pylint: disable=W0602
# ignores no-assignment error since there is one!
import copy
import difflib
import pathlib
import re
import time
import logging
import rx
import rx.operators as ops

from inotify_simple import INotify, flags
from asgiref.sync import async_to_sync

from channels.layers import get_channel_layer
from django.conf import settings

from uploader.models import FirmwareAnalysis


logger = logging.getLogger('web')

# global map for storing messages from all processes
PROCESS_MAP = {}

# EMBAs module count
EMBA_MODULE_CNT = 53


class LogReader:

    def __init__(self, firmware_id):

        # global module count and status_msg directory
        self.module_count = 0
        self.firmware_id = firmware_id
        self.firmware_id_str = str(self.firmware_id)
        try:
            self.firmwarefile = FirmwareAnalysis.objects.get(id=firmware_id).firmware.__str__()
        except Exception as error:
            logger.error("Firmware file exception: %s", error)

        # set variables for channels communication
        self.room_group_name = 'updatesgroup'
        self.channel_layer = get_channel_layer()

        # variables for cleanup
        self.finish = False
        # self.wd = None

        # for testing
        self.test_list1 = []
        self.test_list2 = []

        # status update dict (appended to processmap)
        self.status_msg = {
            "firmwarename": self.firmwarefile,
            "percentage": 0.0,
            "module": "",
            "phase": "",
        }

        # start processing
        time.sleep(1)
        if FirmwareAnalysis.objects.filter(id=self.firmware_id).exists():
            self.read_loop()
        else:
            self.cleanup()

    # update our dict whenever a new module is being processed
    def update_status(self, stream_item_list):
        # progress percentage TODO: improve percentage calculation
        self.module_count += 1

        # calculate percentage
        percentage = self.module_count / EMBA_MODULE_CNT

        # set attributes of current message
        self.status_msg["module"] = stream_item_list[0]
        self.status_msg["percentage"] = percentage

        # get copy of the current status message
        tmp_mes = copy.deepcopy(self.status_msg)

        # append it to the data structure
        global PROCESS_MAP
        if FirmwareAnalysis.objects.filter(id=self.firmware_id).exists():
            found = False
            for mes in PROCESS_MAP[self.firmware_id_str]:
                if mes["phase"] == tmp_mes["phase"] and mes["module"] == tmp_mes["module"]:
                    found = True

            if not found:
                PROCESS_MAP[self.firmware_id_str].append(tmp_mes)

                # send it to room group
                async_to_sync(self.channel_layer.group_send)(
                    self.room_group_name, {
                        "type": 'send.message',
                        "message": PROCESS_MAP
                    }
                )
        else:
            logger.error("Error in update_status, object with id=%s not found", self.firmware_id)

    # update dictionary with phase changes
    def update_phase(self, stream_item_list):
        self.status_msg["phase"] = stream_item_list[1]

        # get copy of the current status message
        tmp_mes = copy.deepcopy(self.status_msg)

        # append it to the data structure
        global PROCESS_MAP
        if FirmwareAnalysis.objects.filter(id=self.firmware_id).exists():
            found = False
            for mes in PROCESS_MAP[self.firmware_id_str]:
                if mes["phase"] == tmp_mes["phase"] and mes["module"] == tmp_mes["module"]:
                    found = True

            if not found:
                PROCESS_MAP[self.firmware_id_str].append(tmp_mes)

                # send it to room group
                async_to_sync(self.channel_layer.group_send)(
                    self.room_group_name, {
                        'type': 'send.message',
                        'message': PROCESS_MAP
                    }
                )
        else:
            logger.error("Error in update_phase, object with id=%s not found", self.firmware_id)
        if "Test ended" in stream_item_list[1]:
            self.finish = True

    def read_loop(self):
        """
        Infinite Loop for waiting for emba.log changes
            :param: None
            :exit condition: Not in this Function, but if emba.sh has terminated this process is also killed
            :return: None
       """
        logger.info("read loop started for %s", self.firmware_id)

        while not self.finish:

            # get firmware for id which the BoundedExecutor gave the log_reader
            firmware = FirmwareAnalysis.objects.get(id=self.firmware_id)

            # if file does not exist create it otherwise delete its content
            pat = f"{settings.EMBA_LOG_ROOT}/{self.firmware_id}/logreader.log"
            if not pathlib.Path(pat).exists():
                with open(pat, 'w+', encoding='utf-8'):
                    pass

            # create an entry for the id in the process map
            global PROCESS_MAP
            if self.firmware_id_str not in PROCESS_MAP:
                PROCESS_MAP[self.firmware_id_str] = []

            # look for new events in log
            logger.debug("looking for events in %s", f"{firmware.path_to_logs}/emba.log")
            got_event = self.inotify_events(f"{firmware.path_to_logs}/emba.log")

            for eve in got_event:
                for flag in flags.from_mask(eve.mask):
                    # Ignore irrelevant flags TODO: add other possible flags
                    if flag is flags.CLOSE_NOWRITE or flag is flags.CLOSE_WRITE:
                        pass
                    # Act on file change
                    elif flag is flags.MODIFY:
                        # get the actual difference
                        tmp = self.get_diff(f"{firmware.path_to_logs}/emba.log")
                        # send changes to frontend
                        self.input_processing(tmp)
                        # copy diff to tmp file
                        self.copy_file_content(tmp)

        self.cleanup()
        logger.info("read loop done for %s", self.firmware_id)
        # return

    def cleanup(self):
        """
        Called when logreader should be cleaned up
        """
        # inotify = INotify()
        # inotify.rm_watch(self.wd)
        logger.debug("Log reader cleaned up for %s", self.firmware_id)
        # TODO do cleanup of emba_new_<self.firmware_id>.log

    @classmethod
    def process_line(cls, inp, pat):

        """
        Regex function for lambda
            :param inp: String to apply regex to
            :param pat: Regex pattern
            :return: True if regex matches otherwise False
        """

        if re.match(pat, inp):
            return True
        # else:
        return False

    def copy_file_content(self, diff):
        """
        Helper function to copy new emba log messages to temporary file continuously
            :param diff: new line in emba log
            :return: None
        """

        with open(f"{settings.EMBA_LOG_ROOT}/{self.firmware_id}/logreader.log", 'a+', encoding='utf-8') as diff_file:
            diff_file.write(diff)

    def get_diff(self, log_file):
        """
        Get diff between two files via difflib
        copied from stack overflow : https://stackoverflow.com/questions/15864641/python-difflib-comparing-files
            :param: None
            :return: result of difflib call without preceding symbols
        """

        # open the two files to get diff from
        logger.debug("getting diff from %s", log_file)
        with open(log_file, encoding='utf-8') as old_file, open(f"{settings.EMBA_LOG_ROOT}/{self.firmware_id}/logreader.log", encoding='utf-8') as new_file:
            diff = difflib.ndiff(old_file.readlines(), new_file.readlines())
            return ''.join(x[2:] for x in diff if x.startswith('- '))

    def input_processing(self, tmp_inp):
        """
        RxPy Function for processing the file diffs and trigger send packet to frontend
            :param tmp_inp: file diff = new line in emba log
            :return: None
        """

        status_pattern = "\\[\\*\\]*"
        phase_pattern = "\\[\\!\\]*"

        color_pattern = "\\x1b\\[.{1,5}m"

        cur_ar = tmp_inp.splitlines()

        # create observable
        source_stream = rx.from_iterable(cur_ar)

        # observer for status messages
        source_stream.pipe(
            ops.map(lambda s: re.sub(color_pattern, '', s)),
            ops.filter(lambda s: self.process_line(s, status_pattern)),
            ops.map(lambda a: a.split("- ")),
            ops.map(lambda t: t[1]),
            ops.map(lambda b: b.split(" ")),
            ops.filter(lambda c: c[1] == 'finished')
        ).subscribe(
            lambda x: [self.update_status(x)]    # , self.test_list1.append(x)]
        )

        # observer for phase messages
        source_stream.pipe(
            ops.map(lambda s: re.sub(color_pattern, '', s)),
            ops.filter(lambda u: self.process_line(u, phase_pattern)),
            ops.map(lambda v: v.split(" ", 1)),
            ops.filter(lambda w: w[1])
        ).subscribe(
            lambda x: [self.update_phase(x)]     # , self.test_list2.append(x)
        )

    @classmethod
    def inotify_events(cls, path):
        # def inotify_events(self, path):
        inotify = INotify()
        watch_flags = flags.CREATE | flags.DELETE | flags.MODIFY | flags.DELETE_SELF | flags.CLOSE_NOWRITE | flags.CLOSE_WRITE
        try:
            # add watch on file
            inotify.add_watch(path, watch_flags)
            return inotify.read()
        except Exception as error:
            logger.error("inotify_event error in %s:%s", path, error)
            return []

    def produce_test_output(self, inp):
        self.input_processing(inp)
        return self.test_list1, self.test_list2

embark/embark/consumers.py METASEP
# import difflib
import json
# import re

import logging

# import rx
# import rx.operators as ops
from asgiref.sync import async_to_sync

from channels.generic.websocket import WebsocketConsumer
from channels.layers import get_channel_layer

# from inotify_simple import flags
# from django.conf import settings
# from uploader.models import Firmware

logger = logging.getLogger('web')


# consumer class for synchronous/asynchronous websocket communication
class WSConsumer(WebsocketConsumer):

    # constructor
    def __init__(self):
        super().__init__()
        self.channel_layer = get_channel_layer()
        self.room_group_name = 'updatesgroup'

    # this method is executed when the connection to the frontend is established
    def connect(self):
        logger.info("WS - connect")
        # create room group for channels communication
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )
        # accept socket connection
        self.accept()
        logger.info("WS - connect - accept")

        # called when received data from frontend
        # TODO: implement this for processing client input at backend -> page refresh should be here

    def receive(self, text_data=None, bytes_data=None):
        logger.info("WS - receive")
        # pass

    # called when websocket connection is closed
    def disconnect(self, code):
        logger.info("WS - disconnected: %s", code)
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    # send data to frontend
    def send_message(self, event):
        # Receive message and extract data from room group
        message = event['message']
        # logger.info(f"WS - send message: " + str(message))
        logger.info("WS - send message")
        # Send message to WebSocket
        self.send(json.dumps(message, sort_keys=False))

embark/embark/asgi.py METASEP
"""
ASGI config for djangoProject project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
"""

import os

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter
from channels.routing import URLRouter
from django.core.asgi import get_asgi_application

from .routing import ws_urlpatterns

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'embark.settings.deploy')
asgi_application = get_asgi_application()

application = ProtocolTypeRouter({
    'http': asgi_application,
    'websocket': AuthMiddlewareStack(URLRouter(ws_urlpatterns))
})

embark/embark/__init__.py METASEP

embark/dashboard/views.py METASEP
import logging
import os
import signal
import psutil

from django.conf import settings
from django.shortcuts import render
from django.http import HttpResponseBadRequest, HttpResponseForbidden, HttpResponseRedirect, HttpResponseServerError
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods

from uploader.models import FirmwareAnalysis
from .models import Result
from .forms import StopAnalysisForm

logger = logging.getLogger('web')


@require_http_methods(["GET"])
@login_required(login_url='/' + settings.LOGIN_URL)
def main_dashboard(request):
    if request.user.is_authenticated:
        if Result.objects.all().count() > 0:
            return render(request, 'dashboard/mainDashboard.html', {'nav_switch': True, 'username': request.user.username})
        return HttpResponseRedirect('../../uploader/')
    return HttpResponseForbidden


@login_required(login_url='/' + settings.LOGIN_URL)
@require_http_methods(["POST"])
def stop_analysis(request):
    """
    View to submit form for flags to run emba with
    if: form is valid
        send interrupt to analysis.pid
    Args:
        request: the http req with FirmwareForm
    Returns: redirect
    """
    form = StopAnalysisForm(request.POST)
    if form.is_valid():
        logger.debug("Posted Form is valid")
        # get id
        analysis = form.cleaned_data['analysis']
        logger.info("Stopping analysis with id %s", analysis.id)
        pid = FirmwareAnalysis.objects.get(id=analysis.id).pid
        logger.debug("PID is %s", pid)
        try:
            os.killpg(os.getpgid(pid), signal.SIGTERM)
            for proc in psutil.process_iter():
                # check whether the process name matches
                if proc.name() == analysis.id:
                    proc.kill()
            form = StopAnalysisForm()
            form.fields['analysis'].queryset = FirmwareAnalysis.objects.filter(finished=False)
            return render(request, 'dashboard/serviceDashboard.html', {'username': request.user.username, 'form': form, 'success_message': True, 'message': "Stopped successfully"})
        except Exception as error:
            logger.error("Error %s", error)
            return HttpResponseServerError("Failed to stop procs, because" + str(error))
    return HttpResponseBadRequest("invalid form")


@require_http_methods(["GET"])
@login_required(login_url='/' + settings.LOGIN_URL)
def service_dashboard(request):
    """
    showing running emba analysis
    uses ws/wss for progress
    :params request: req
    :return httpresp: html servicedashboard
    """
    # if FirmwareAnalysis.objects.all().count() > 0:
    form = StopAnalysisForm()
    form.fields['analysis'].queryset = FirmwareAnalysis.objects.filter(finished=False)
    return render(request, 'dashboard/serviceDashboard.html', {'username': request.user.username, 'form': form, 'success_message': False})


@require_http_methods(["GET"])
@login_required(login_url='/' + settings.LOGIN_URL)
def report_dashboard(request):
    """
    delivering ReportDashboard with finished_firmwares as dictionary

    :params request: HTTP request

    :return: rendered ReportDashboard
    """
    finished_firmwares = FirmwareAnalysis.objects.filter(failed=False, finished=True)
    return render(request, 'dashboard/reportDashboard.html', {'finished_firmwares': finished_firmwares, 'username': request.user.username})


@login_required(login_url='/' + settings.LOGIN_URL)
def individual_report_dashboard(request, analysis_id):
    """
    delivering individualReportDashboard

    :params request id: HTTP request, hashid of the firmware_analysis

    :return: rendered individualReportDashboard of Results for fw_analysis
    """
    logger.info("individual_dashboard - analyze_id: %s", analysis_id)
    return render(request, 'dashboard/individualReportDashboard.html', {'username': request.user.username, 'analysis_id': analysis_id})

embark/dashboard/urls.py METASEP
from django.urls import path

from . import views


# view routing
urlpatterns = [
    path('dashboard/main/', views.main_dashboard, name='embark-MainDashboard'),
    path('dashboard/service/', views.service_dashboard, name='embark-ServiceDashboard'),
    path('dashboard/report/', views.report_dashboard, name='embark-ReportDashboard'),
    path('dashboard/individualReport/<uuid:analysis_id>', views.individual_report_dashboard, name='embark-IndividualReportDashboard'),
    path('dashboard/stop/', views.stop_analysis, name='embark-stop-analysis')
]

embark/dashboard/tests.py METASEP
# from django.test import TestCase

# TODO Create multiple results and test render

embark/dashboard/models.py METASEP
from django.db import models

from uploader.models import FirmwareAnalysis


class Result(models.Model):
    firmware_analysis = models.ForeignKey(FirmwareAnalysis, on_delete=models.CASCADE, help_text='')
    architecture_verified = models.CharField(blank=True, null=True, max_length=100, help_text='')
    os_verified = models.CharField(blank=True, null=True, max_length=100, help_text='')
    emba_command = models.CharField(blank=True, null=True, max_length=300, help_text='')
    files = models.IntegerField(default=0, help_text='')
    directories = models.IntegerField(default=0, help_text='')
    entropy_value = models.FloatField(default=0.0, help_text='')
    certificates = models.IntegerField(default=0, help_text='')
    certificates_outdated = models.IntegerField(default=0, help_text='')
    shell_scripts = models.IntegerField(default=0, help_text='')
    shell_script_vulns = models.IntegerField(default=0, help_text='')
    yara_rules_match = models.IntegerField(default=0, help_text='')
    kernel_modules = models.IntegerField(default=0, help_text='')
    kernel_modules_lic = models.IntegerField(default=0, help_text='')
    interesting_files = models.IntegerField(default=0, help_text='')
    post_files = models.IntegerField(default=0, help_text='')
    canary = models.IntegerField(default=0, help_text='')
    canary_per = models.IntegerField(default=0, help_text='')
    relro = models.IntegerField(default=0, help_text='')
    relro_per = models.IntegerField(default=0, help_text='')
    no_exec = models.IntegerField(default=0, help_text='')
    no_exec_per = models.IntegerField(default=0, help_text='')
    pie = models.IntegerField(default=0, help_text='')
    pie_per = models.IntegerField(default=0, help_text='')
    stripped = models.IntegerField(default=0, help_text='')
    stripped_per = models.IntegerField(default=0, help_text='')
    strcpy = models.IntegerField(default=0, help_text='')
    versions_identified = models.IntegerField(default=0, help_text='')
    cve_high = models.IntegerField(default=0, help_text='')
    cve_medium = models.IntegerField(default=0, help_text='')
    cve_low = models.IntegerField(default=0, help_text='')
    exploits = models.IntegerField(default=0, help_text='')
    metasploit_modules = models.IntegerField(default=0, help_text='')
    bins_checked = models.IntegerField(default=0, help_text='')
    strcpy_bin = models.TextField(default='{}')

embark/dashboard/forms.py METASEP
import logging
from django import forms

from uploader.models import FirmwareAnalysis

logger = logging.getLogger('web')


class StopAnalysisForm(forms.Form):
    analysis = forms.ModelChoiceField(queryset=FirmwareAnalysis.objects, empty_label='Select Analysis to stop')

embark/dashboard/apps.py METASEP
from django.apps import AppConfig


class DashboardConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'dashboard'

embark/dashboard/admin.py METASEP
from django.contrib import admin

from .models import Result

admin.register(Result)

embark/dashboard/__init__.py METASEP

embark/entrypoint.sh METASEP
#!/bin/bash

export DJANGO_SETTINGS_MODULE=embark.settings.docker # TODO

if ! [[ -d logs ]]; then
  mkdir logs
fi

echo -e "[*] Setup logging of redis database - log to ./logs/redis_db.log"
docker container logs embark_redis_1 -f > ./logs/redis_db.log &
echo -e "[*] Setup logging of mysql database - log to ./logs/mysql_db.log"
docker container logs embark_auth-db_1 -f > ./logs/mysql_db.log &
echo -e "[*] Starting migrations - log to ./logs/migration.log"
python3 manage.py makemigrations users uploader | tee -a ./logs/migration.log
python3 manage.py migrate | tee -a ./logs/migration.log
echo -e "[*] Starting runapscheduler"
python3 manage.py runapscheduler --test | tee -a ./logs/migration.log &
echo -e "[*] Starting uwsgi - log to ./logs/uwsgi.log"
uwsgi --wsgi-file /app/embark/embark/wsgi.py --http :80 --processes 2 --threads 10 --logto ./logs/uwsgi.log &
echo -e "[*] Starting daphne - log to ./logs/daphne.log"
# shellcheck disable=2094
#TODO the bind address has to be set at runtime or dns-res
daphne -v 3 --access-log ./logs/daphne.log embark.asgi:application -p 8001 -b '0.0.0.0' &> ./logs/daphne.log

dev-tools/debug-server-start.sh METASEP
#!/bin/bash
# EMBArk - The firmware security scanning environment
#
# Copyright 2020-2021 Siemens Energy AG
# Copyright 2020-2021 Siemens AG
#
# EMBArk comes with ABSOLUTELY NO WARRANTY.
#
# EMBArk is licensed under MIT
#
# Author(s): Benedikt Kuehne

# Description: Automates setup of developer environment for Debug-Server

# RED='\033[0;31m'
GREEN='\033[0;32m'
ORANGE='\033[0;33m'
# BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m' # no color

PORT="8000"
IP="127.0.0.1"


export DJANGO_SETTINGS_MODULE=embark.settings.dev
export EMBARK_DEBUG=True
export PIPENV_VENV_IN_PROJECT="True"

cleaner() {
  if [[ -f ./embark/embark.log ]]; then
    rm ./embark/embark.log -f
  fi
  
  # killall -9 -q "*daphne*"
  docker container stop embark_db_dev
  docker container stop embark_redis_dev

  docker container prune -f --filter "label=flag"

  fuser -k "$PORT"/tcp
  exit 1
}

set -a
trap cleaner INT

cd "$(dirname "$0")" || exit 1
cd .. || exit 1

echo -e "\n$GREEN""$BOLD""Configuring Embark""$NC"

# shellcheck disable=SC1091
source ./.venv/bin/activate || exit 1

echo -e "\n$GREEN""$BOLD""Setup mysql and redis docker images""$NC"
docker-compose -f ./docker-compose-dev.yml up -d
DU_RETURN=$?
if [[ $DU_RETURN -eq 0 ]] ; then
  echo -e "$GREEN""$BOLD""Finished setup mysql and redis docker images""$NC"
else
  echo -e "$ORANGE""$BOLD""Failed setup mysql and redis docker images""$NC"
fi

if ! [[ -d ./logs ]]; then
  mkdir ./logs
fi

# db_init
echo -e "[*] Starting migrations - log to embark/logs/migration.log"
pipenv run ./embark/manage.py makemigrations users uploader reporter dashboard | tee -a ./logs/migration.log
pipenv run ./embark/manage.py migrate | tee -a ./logs/migration.log

# superuser
pipenv run ./embark/manage.py createsuperuser --noinput

echo -e "\n[""$BLUE JOB""$NC""] Redis logs are copied to ./embark/logs/redis_dev.log""$NC" 
docker container logs embark_redis_dev -f > ./logs/redis_dev.log &
echo -e "\n[""$BLUE JOB""$NC""] DB logs are copied to ./embark/logs/mysql_dev.log""$NC"
docker container logs embark_db_dev -f > ./logs/mysql_dev.log & 

##
echo -e "\n[""$BLUE JOB""$NC""] Starting runapscheduler"
pipenv run ./embark/manage.py runapscheduler | tee -a ./logs/scheduler.log &

# echo -e "\n[""$BLUE JOB""$NC""] Starting daphne(ASGI) - log to /embark/logs/daphne.log"
# echo "START DAPHNE" >./logs/daphne.log
# cd ./embark || exit 1
# pipenv run daphne -v 3 -p 8001 -b "$IP" --root-path="$PWD"/embark embark.asgi:application &>../logs/daphne.log &
# cd .. || exit 1

# start embark
systemctl start embark.service
echo -e "$ORANGE""$BOLD""start EMBArk server (WS/WSS not enabled -a also asgi)""$NC"
pipenv run ./embark/manage.py runserver "$IP":"$PORT" |& tee -a ./logs/debug-server.log

wait

echo -e "\n$ORANGE""$BOLD""Done. To clean-up use the clean-setup script""$NC"
dev-tools/clean-setup.sh METASEP
#!/bin/bash
# EMBArk - The firmware security scanning environment
#
# Copyright 2020-2021 Siemens Energy AG
# Copyright 2020-2021 Siemens AG
#
# EMBArk comes with ABSOLUTELY NO WARRANTY.
#
# EMBArk is licensed under MIT
#
# Author(s): Benedikt Kuehne

# Description:  Script for cleaning up all docker- container and images
cd "$(dirname "$0")" || exit 1
echo -e "\n$GREEN""$BOLD""Reset docker container & networks""$NC"

docker images
docker container ls -a

while docker images | grep -qE "\<none\>"; do
  IMAGE_ID=$(docker images | grep -E "\<none\>" | awk '{print $3}')
  echo -e "$GREEN""$BOLD""Remove failed docker image""$NC"
  docker image rm "$IMAGE_ID" -f
done

if docker images | grep -qE "^embeddedanalyzer/emba"; then
  echo -e "\n$GREEN""$BOLD""Found EMBA docker environment - removing it""$NC"
  CONTAINER_ID=$(docker images | grep -E "embeddedanalyzer/emba" | awk '{print $3}')
  echo -e "$GREEN""$BOLD""Remove EMBA docker image""$NC"
  docker image rm "$IMAGE_ID" -f
fi

if docker images | grep -qE "^embark[[:space:]]*latest"; then
  echo -e "\n$GREEN""$BOLD""Found EMBArk docker environment - removing it""$NC"
  CONTAINER_ID=$(docker container ls -a | grep -E "embark_embark_1" | awk '{print $1}')
  echo -e "$GREEN""$BOLD""Stop EMBArk docker container""$NC"
  docker container stop "$CONTAINER_ID"
  echo -e "$GREEN""$BOLD""Remove EMBArk docker container""$NC"
  docker container rm "$CONTAINER_ID" -f
  echo -e "$GREEN""$BOLD""Remove EMBArk docker image""$NC"
  docker image rm embark:latest -f
fi

if docker images | grep -qE "^mysql[[:space:]]*latest"; then
  echo -e "\n$GREEN""$BOLD""Found mysql docker environment - removing it""$NC"
  CONTAINER_ID=$(docker container ls -a | grep -E "embark_db" | awk '{print $1}')
  echo -e "$GREEN""$BOLD""Stop mysql docker container""$NC"
  docker container stop "$CONTAINER_ID"
  echo -e "$GREEN""$BOLD""Remove mysql docker container""$NC"
  docker container rm "$CONTAINER_ID" -f
  echo -e "$GREEN""$BOLD""Remove mysql docker image""$NC"
  docker image rm mysql:latest -f
fi

if docker images | grep -qE "^redis[[:space:]]*5"; then
  echo -e "\n$GREEN""$BOLD""Found redis docker environment - removing it""$NC"
  CONTAINER_ID=$(docker container ls -a | grep -E "embark_redis" | awk '{print $1}')
  echo -e "$GREEN""$BOLD""Stop redis docker container""$NC"
  docker container stop "$CONTAINER_ID"
  echo -e "$GREEN""$BOLD""Remove redis docker container""$NC"
  docker container rm "$CONTAINER_ID" -f
  echo -e "$GREEN""$BOLD""Remove redis docker image""$NC"
  docker image rm redis:5 -f
fi

#networks

if docker network ls | grep -E "embark_dev"; then
  echo -e "\n$GREEN""$BOLD""Found EMBArk_dev network - removing it""$NC"
  NET_ID=$(docker network ls | grep -E "embark_dev" | awk '{print $1}')
  echo -e "$GREEN""$BOLD""Remove EMBArk_dev network""$NC"
  docker network rm "$NET_ID" 
fi

if docker network ls | grep -E "embark_frontend"; then
  echo -e "\n$GREEN""$BOLD""Found EMBArk_frontend network - removing it""$NC"
  NET_ID=$(docker network ls | grep -E "embark_frontend" | awk '{print $1}')
  echo -e "$GREEN""$BOLD""Remove EMBArk_frontend network""$NC"
  docker network rm "$NET_ID" 
fi

if docker network ls | grep -E "embark_backend"; then
  echo -e "\n$GREEN""$BOLD""Found EMBArk_backend network - removing it""$NC"
  NET_ID=$(docker network ls | grep -E "embark_backend" | awk '{print $1}')
  echo -e "$GREEN""$BOLD""Remove EMBArk_backend network""$NC"
  docker network rm "$NET_ID" 
fi

fuser -k 8001/tcp
fuser -k 80/tcp
fuser -k 8080/tcp
docker container prune

echo -e "$ORANGE""$BOLD""Consider running \$docker system prune""$NC"
dev-tools/check_project.sh METASEP
#!/bin/bash

# EMBArk - The firmware security scanning environment
#
# Copyright 2020-2021 Siemens Energy AG
# Copyright 2020-2021 Siemens AG
#
# EMBArk comes with ABSOLUTELY NO WARRANTY.
#
# EMBArk is licensed under MIT
#
# Author(s): Michael Messner, Pascal Eckmann
# Contributer(s): Benedikt Kuehne

# Description:  Check all scripts and templates(Django gets its own unit-tests)
#               And check Django if its deployable

export DJANGO_SETTINGS_MODULE=embark.settings.dev
export PYTHONPATH="$PYTHONPATH:${PWD}/embark/embark/"
export PIPENV_VENV_IN_PROJECT="True"

cd "$(dirname "$0")" || exit 1
cd .. || exit 1 

RED='\033[0;31m'
GREEN='\033[0;32m'
ORANGE='\033[0;33m'
BOLD='\033[1m'
NC='\033[0m' # no color

if [[ $EUID -eq 0 ]]; then
  echo -e "\\n$RED""Running this script as root is not supported""$NC\\n"
fi

# check that all tools are installed
check_tools(){
  TOOLS=("jshint" "shellcheck" "pylint")
  for TOOL in "${TOOLS[@]}";do
    if ! command -v "$TOOL" > /dev/null ; then 
      echo -e "\\n""$RED""$TOOL is not installed correctly""$NC""\\n"
      exit 1
    fi
  done
  pipenv run djlint --version | grep "version"; RES=$?
  if [[ -z "$RES" ]];then
    echo -e "\\n""$RED""djlint(pip) is not installed correctly""$NC""\\n"
  fi
}

# checks django configuration
check_django(){
  cd ./embark || exit 1
  echo -e "\\n""$ORANGE""$BOLD""EMBArk django-settings check""$NC""\\n""$BOLD""=================================================================""$NC"
  if ! pipenv run python ./manage.py check | grep -q "System check identified no issues"; then  #TODO add --deploy flag
      echo -e "\\n""$RED$BOLD==> FIX ERRORS""$NC""\\n"
      ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
      MODULES_TO_CHECK_ARR+=( "./embark/settings.py" )
  else
      echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
  fi
  cd .. || exit 1
}

# checks js-scripts with jshint for errors
# config @ .jshintrc
jscheck(){
  echo -e "\\n""$ORANGE""$BOLD""EMBArk javascript-files check""$NC""\\n""$BOLD""=================================================================""$NC"
  mapfile -t JS_SCRIPTS < <(find ./embark -iname "*.js")
  for JS_SCRIPT in "${JS_SCRIPTS[@]}"; do
    echo -e "\\n""$GREEN""Run jshint on $JS_SCRIPT:""$NC""\\n"
    # mapfile -t JS_RESULT < <(jshint "$JS_SCRIPT")
    jshint -c ./.jshintrc "$JS_SCRIPT"
    RES=$?
    if [[ $RES -eq 2 ]] ; then
      echo -e "\\n""$RED$BOLD==> FIX ERRORS""$NC""\\n"
      ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
      MODULES_TO_CHECK_ARR+=( "$JS_SCRIPT" )
    elif [[ $RES -eq 1 ]]; then
      echo -e "\\n""$ORANGE$BOLD==> FIX WARNINGS""$NC""\\n"
      ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
      MODULES_TO_CHECK_ARR+=( "$JS_SCRIPT" )
    elif [[ $RES -eq 0 ]]; then 
      echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
    else 
      echo -e "\\n""$RED$BOLD""[jshint]ERRORS in SCRIPT""$NC""\\n"
    fi
  done
}

# uses djlint to check for errors in all html-template files inside /embark (Django-root-dir)
# no config
templatechecker(){
  echo -e "\\n""$ORANGE""$BOLD""EMBArk html-templates check""$NC""\\n""$BOLD""=================================================================""$NC"
  mapfile -t HTML_FILE < <(find ./embark -iname "*.html")
  for HTML_FILE in "${HTML_FILE[@]}"; do
    echo -e "\\n""$GREEN""Run djlint on $HTML_FILE:""$NC""\\n"
    pipenv run djlint "$HTML_FILE"
    RES=$?
    if [[ $RES -eq 1 ]]; then
      echo -e "\\n""$ORANGE$BOLD==> FIX ERRORS""$NC""\\n"
      ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
      MODULES_TO_CHECK_ARR+=( "$HTML_FILE" )
    elif [[ $RES -eq 0 ]]; then 
      echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
    else 
      echo -e "\\n""$RED$BOLD""[html-check(tidy)]ERRORS in SCRIPT""$NC""\\n"
    fi
  done
}

shellchecker() {
  echo -e "\\n""$ORANGE""$BOLD""EMBArk Shellcheck""$NC""\\n""$BOLD""=================================================================""$NC"
  if ! command -v shellcheck >/dev/null 2>&1; then
    echo -e "\\n""$ORANGE""Shellcheck not found!""$NC""\\n""$ORANGE""Install shellcheck via 'apt-get install shellcheck'!""$NC\\n"
    exit 1
  fi

  echo -e "\\n""$GREEN""Run shellcheck on installer:""$NC""\\n"
  if shellcheck ./installer.sh || [[ $? -ne 1 && $? -ne 2 ]]; then
    echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
  else
    echo -e "\\n""$ORANGE$BOLD==> FIX ERRORS""$NC""\\n"
    ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
    MODULES_TO_CHECK_ARR+=( "installer.sh" )
  fi

  echo -e "\\n""$GREEN""Run shellcheck on this script:""$NC""\\n"
  if shellcheck ./dev-tools/check_project.sh || [[ $? -ne 1 && $? -ne 2 ]]; then
    echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
  else
    echo -e "\\n""$ORANGE$BOLD==> FIX ERRORS""$NC""\\n"
    ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
    MODULES_TO_CHECK_ARR+=( "dev-tools/check_project.sh" )
  fi

  echo -e "\\n""$GREEN""Find shell scripts and run shellcheck on them:""$NC""\\n"
  mapfile -t SH_SCRIPTS < <(find embark -iname "*.sh")
  for SH_SCRIPT in "${SH_SCRIPTS[@]}"; do
    echo -e "\\n""$GREEN""Run shellcheck on $SH_SCRIPT:""$NC""\\n"
    if shellcheck -x "$SH_SCRIPT" || [[ $? -ne 1 && $? -ne 2 ]]; then
      echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
    else
      echo -e "\\n""$ORANGE$BOLD==> FIX ERRORS""$NC""\\n"
      ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
      MODULES_TO_CHECK_ARR+=( "$SH_SCRIPT" )
    fi
  done
}

pycodestyle_check(){
  echo -e "\\n""$ORANGE""$BOLD""EMBArk pycodestyle check""$NC""\\n""$BOLD""=================================================================""$NC"
  echo -e "[*] Searching python files and test with pycodestyle.py"
  if command -v pipenv run pycodstyle >/dev/null 2>&1; then
    echo -e "\\n""$ORANGE""pycodestyle found in PATH!""$NC""\\n"
    PYCODESTYLE="pycodestyle"
  elif [[ -f "/usr/lib/python3/dist-packages/pycodestyle.py" ]]; then
    echo -e "\\n""$ORANGE""pycodestyle found!""$NC""\\n"
    PYCODESTYLE="/usr/lib/python3/dist-packages/pycodestyle.py"
  else
    echo -e "\\n""$ORANGE""pycodestyle not found!""$NC""\\n""$ORANGE""Install pycodestyle via 'apt-get install pycodestyle'!""$NC\\n"
    exit 1
  fi

  echo -e "\\n""$GREEN""Find python scripts and run pycodestyle on them:""$NC""\\n"
  mapfile -t PY_SCRIPTS < <(find embark -iname "*.py")
  for PY_SCRIPT in "${PY_SCRIPTS[@]}"; do
    echo -e "\\n""$GREEN""Run pycodestyle on $PY_SCRIPT:""$NC""\\n"
    if pipenv run "$PYCODESTYLE" --config=./.pycodestylerc --first "$PY_SCRIPT" 2> >(grep -v "Courtesy Notice\|Loading .env" >&2) || [[ $? -ne 1 && $? -ne 2 ]]; then
      echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
    else
      echo -e "\\n""$ORANGE$BOLD==> FIX ERRORS""$NC""\\n"
      ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
      MODULES_TO_CHECK_ARR+=( "$PY_SCRIPT" )
    fi
  done
}

banditer() {
  echo -e "\\n""$ORANGE""$BOLD""EMBArk bandit check""$NC""\\n""$BOLD""=================================================================""$NC"
  if command -v bandit >/dev/null 2>&1; then
    echo -e "\\n""$ORANGE""bandit found in PATH!""$NC""\\n"
  else
    echo -e "\\n""$ORANGE""bandit not found!""$NC""\\n""$ORANGE""Install bandit via 'apt-get install bandit'!""$NC\\n"
    exit 1
  fi

  for PY_SCRIPT in "${PY_SCRIPTS[@]}"; do
    echo -e "\\n""$GREEN""Run bandit on $PY_SCRIPT:""$NC""\\n"
    if bandit -c .banditrc "$PY_SCRIPT" 2> /dev/null | grep -q "No issues identified."; then
      echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
    else
      bandit -c .banditrc "$PY_SCRIPT"
      echo -e "\\n""$ORANGE$BOLD==> FIX ERRORS""$NC""\\n"
      ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
      MODULES_TO_CHECK_ARR+=( "$PY_SCRIPT" )
    fi
  done

}

pylinter(){
  cd ./embark || exit 1
  echo -e "\\n""$ORANGE""$BOLD""EMBArk pylint check""$NC""\\n""$BOLD""=================================================================""$NC"
  mapfile -t PY_SCRIPTS < <(find . -type d -name migrations -prune -false -o -iname "*.py")
  for PY_SCRIPT in "${PY_SCRIPTS[@]}"; do
    echo -e "\\n""$GREEN""Run pylint on $PY_SCRIPT:""$NC""\\n"
    mapfile -t PY_RESULT < <(pipenv run pylint --rcfile=../.pylintrc "$PY_SCRIPT" 2> >(grep -v "Courtesy Notice\|Loading .env" >&2) )
    local RATING_10=0
    if [[ "${#PY_RESULT[@]}" -gt 0 ]]; then 
      if ! printf '%s\n' "${PY_RESULT[@]}" | grep -q -P '^Your code has been rated at 10'; then
        for LINE in "${PY_RESULT[@]}"; do
          echo "$LINE"
        done
      else
        RATING_10=1
      fi
      if [[ "$RATING_10" -ne 1 ]]; then
        echo -e "\\n""$ORANGE$BOLD==> FIX ERRORS""$NC""\\n"
        ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
        MODULES_TO_CHECK_ARR+=( "$PY_SCRIPT" )
      else
        echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
      fi
    else
      echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
    fi
  done

  echo -e "\\n""$GREEN""Run pylint on all scripts:""$NC""\\n"
  pipenv run pylint --rcfile=../.pylintrc ./*  2> >(grep -v "Courtesy Notice\|Loading .env" >&2) | grep "Your code has been rated"
  cd .. || exit 1
}

dockerchecker(){
  if ! [[ -f .env ]]; then
    ENV=1
    touch .env    #dummy file
  else
    ENV=0
  fi
  echo -e "\\n""$ORANGE""$BOLD""EMBArk docker-files check""$NC""\\n""$BOLD""=================================================================""$NC"
  mapfile -t DOCKER_COMPS < <(find . -maxdepth 1 -type d -name migrations -prune -false -o -iname "docker-compose*.yml")
  for DOCKER_COMP in "${DOCKER_COMPS[@]}"; do
    echo -e "\\n""$GREEN""Run docker check on $DOCKER_COMP:""$NC""\\n"
    if docker-compose -f "$DOCKER_COMP" config 1>/dev/null || [[ $? -ne 1 ]]; then
      echo -e "$GREEN""$BOLD""==> SUCCESS""$NC""\\n"
    else
      echo -e "\\n""$ORANGE$BOLD==> FIX ERRORS""$NC""\\n"
      ((MODULES_TO_CHECK=MODULES_TO_CHECK+1))
      MODULES_TO_CHECK_ARR+=( "$DOCKER_COMP" )    
    fi
  done
  #TODO dockerlinter -f ./Dockerfile
  if [[ $ENV -gt 0 ]]; then
    rm .env   #remove dummy
  fi
}

#main
check_tools
MODULES_TO_CHECK=0
MODULES_TO_CHECK_ARR=()
shellchecker
dockerchecker
jscheck
templatechecker
pycodestyle_check
banditer
pylinter
check_django

if [[ "${#MODULES_TO_CHECK_ARR[@]}" -gt 0 ]]; then
  echo -e "\\n\\n""$GREEN$BOLD""SUMMARY:$NC\\n"
  echo -e "Modules to check: $MODULES_TO_CHECK\\n"
  for MODULE in "${MODULES_TO_CHECK_ARR[@]}"; do
    echo -e "$RED$BOLD==> FIX MODULE: ""$MODULE""$NC"
  done
  exit 1
fi
echo -e "$GREEN$BOLD===> ALL CHECKS SUCCESSFUL""$NC"
exit 0
cert/server.cnf METASEP
HOME            = .

####################################################################
[ req ]
default_bits       = 2048
default_keyfile    = embark.key
distinguished_name = server_distinguished_name
req_extensions     = server_req_extensions
string_mask        = utf8only

####################################################################
[ server_distinguished_name ]
countryName         = Country Name (2 letter code)
countryName_default = US

stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = MD

localityName         = Locality Name (eg, city)
localityName_default = Baltimore

organizationName            = Organization Name (eg, company)
organizationName_default    = EMBA

commonName           = Common Name (e.g. server FQDN or YOUR name)
commonName_default   = EMBArk Server

emailAddress         = Email Address
emailAddress_default = no@mail.com

####################################################################
[ server_req_extensions ]

subjectKeyIdentifier = hash
basicConstraints     = CA:FALSE
keyUsage             = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName       = @alternate_names
nsComment            = "OpenSSL Generated Certificate"

####################################################################
[ alternate_names ]

DNS.1  = embark.local
IP.1 = 0.0.0.0
IP.2 = 127.0.0.1
cert/serial.txt METASEP
01

cert/openssl-ca.cnf METASEP
HOME            = .

####################################################################
[ ca ]
default_ca    = CA_default      # The default ca section

[ CA_default ]
base_dir      = .
certificate   = $base_dir/ca.crt   # The CA certifcate
private_key   = $base_dir/ca.key   # The CA private key
new_certs_dir = $base_dir              # Location for new certs after signing
database      = $base_dir/index.txt    # Database index file
serial        = $base_dir/serial.txt   # The current serial number

policy  =   signing_policy

unique_subject = no  # Set to 'no' to allow creation of
                     # several certificates with same subject.
default_days     = 10000         # How long to certify for
default_crl_days = 300           # How long before next CRL
default_md       = sha256       # Use public key default MD
preserve         = no           # Keep passed DN ordering

x509_extensions = ca_extensions # The extensions to add to the cert

email_in_dn     = no            # Don't concat the email in the DN
copy_extensions = copy          # Required to copy SANs from CSR to cert

####################################################################
[ req ]
default_bits       = 4096
default_keyfile    = ca.key
distinguished_name = ca_distinguished_name
x509_extensions    = ca_extensions
string_mask        = utf8only

####################################################################
[ ca_distinguished_name ]
countryName         = Country Name (2 letter code)
countryName_default = US

stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = Maryland

localityName                = Locality Name (eg, city)
localityName_default        = Baltimore

organizationName            = Organization Name (eg, company)
organizationName_default    = Test CA, Limited

organizationalUnitName         = Organizational Unit (eg, division)
organizationalUnitName_default = Server Research Department

commonName         = Common Name (e.g. server FQDN or YOUR name)
commonName_default = Test CA

emailAddress         = Email Address
emailAddress_default = test@example.com

####################################################################
[ ca_extensions ]

subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints       = critical, CA:true
keyUsage               = keyCertSign, cRLSign

[ crl_ext ]

# CRL extensions.
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.

# issuerAltName=issuer:copy
authorityKeyIdentifier=keyid:always

####################################################################
[ signing_policy ]
countryName            = optional
stateOrProvinceName    = optional
localityName           = optional
organizationName       = optional
organizationalUnitName = optional
commonName             = supplied
emailAddress           = optional

####################################################################
[ signing_req ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment
cert/index.txt METASEP


embark/manage.py METASEP
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
# import logging


def main():
    """Run administrative tasks."""
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'embark.settings')

    try:
        # pylint: disable=import-outside-toplevel
        from django.core.management import execute_from_command_line
        # pylint: enable=import-outside-toplevel
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

supervisor.sh METASEP
#!/bin/bash
# EMBArk - The firmware security scanning environment
#
# Copyright 2020 - 2022 Siemens Energy AG
#
# EMBArk comes with ABSOLUTELY NO WARRANTY.
#
# EMBArk is licensed under MIT
#
# Author(s): Benedikt Kuehne

# Description: Supervisor for EMBArk server

LOG_FILE='/var/log/embark-supervisor.log'

# Close standard output file descriptors and reroute to log
exec 1<&-
exec 2<&-
exec 1<>$LOG_FILE
exec 2>&1

while :; do
  if ! ip a show embark_backend | grep -q "172.22.0.1" || ip a show embark_dev | grep -q "172.21.0.1"; then
    systemctl restart NetworkManager docker
    echo "$(date +"%D %T")""retstarted docker-networks"
  fi
  sleep 1m
done
run-server.sh METASEP
#!/bin/bash
# EMBArk - The firmware security scanning environment
#
# Copyright 2020-2021 Siemens Energy AG
# Copyright 2020-2021 Siemens AG
#
# EMBArk comes with ABSOLUTELY NO WARRANTY.
#
# EMBArk is licensed under MIT
#
# Author(s): Benedikt Kuehne

# Description: Starts the EMBArk on host

GREEN='\033[0;32m'
ORANGE='\033[0;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'

export DJANGO_SETTINGS_MODULE=embark.settings.deploy
export EMBARK_DEBUG=True
export HTTP_PORT=80
export HTTPS_PORT=443
export BIND_IP='0.0.0.0'
export FILE_SIZE=2000000000


cleaner() {
  pkill -u root daphne
  pkill -u root /app/emba/emba.sh
  pkill -u root runapscheduler

  fuser -k "$HTTP_PORT"/tcp
  fuser -k "$HTTPS_PORT"/tcp
  fuser -k 8000/tcp
  fuser -k 8001/tcp

  docker container stop embark_db
  docker container stop embark_redis
  docker network rm embark_backend
  docker container prune -f --filter "label=flag"

  systemctl stop embark.service
  systemctl disable embark.service
  exit 1
}

# main
set -a
trap cleaner INT

cd "$(dirname "$0")" || exit 1

if ! [[ $EUID -eq 0 ]] ; then
  echo -e "\\n$RED""Run EMBArk installation script with root permissions!""$NC\\n"
  exit 1
fi

# check emba
echo -e "$BLUE""$BOLD""checking EMBA""$NC"
/app/emba/emba.sh -d
if [[ $? -eq 1 ]]; then
  echo -e "$BLUE""Trying auto-maintain""$NC"
  # automaintain
  if ! [[ -d ./emba ]]; then
    echo -e "$RED""EMBA not installed""$NC"
    exit 1
  fi
  cd ./emba || exit 1
  systemctl restart NetworkManager docker
  ./emba.sh -d 1>/dev/null
  if [[ $? -eq 1 ]]; then
    echo -e "$RED""EMBA is not configured correctly""$NC"
    exit 1
  fi
  cd .. || exit 1
fi

# start venv (ignore source in script)
# shellcheck disable=SC1091
source ./.venv/bin/activate || exit 1

# Start container
echo -e "\n$GREEN""$BOLD""Setup mysql and redis docker images""$NC"
docker-compose -f ./docker-compose.yml up -d
DU_RETURN=$?
if [[ $DU_RETURN -eq 0 ]] ; then
  echo -e "$GREEN""$BOLD""Finished setup mysql and redis docker images""$NC"
else
  echo -e "$ORANGE""$BOLD""Failed setup mysql and redis docker images""$NC"
fi

if ! [[ -d /app/www/logs ]]; then
  mkdir /app/www/logs
fi

if ! [[ -d /app/www/conf ]]; then
  mkdir /app/www/conf
fi

# container-logs (2 jobs)
echo -e "\n[""$BLUE JOB""$NC""] Redis logs are copied to ./embark/logs/redis_dev.log" 
docker container logs embark_redis -f &> /app/www/logs/redis.log & 
echo -e "\n[""$BLUE JOB""$NC""] DB logs are copied to ./embark/logs/mysql_dev.log"
docker container logs embark_db -f &> /app/www/logs/mysql.log &

#start the supervisor
systemctl enable embark.service
systemctl start embark.service

# copy django server
if [[ -d /app/www/embark ]]; then
  rm -R /app/www/embark
fi
cp -Ru ./embark/ /app/www/embark/

# config apache
# add all modules we want (mod_ssl mod_auth_basic etc)
# post_max_size increase
{
  echo ''
} > /app/www/conf/embark.conf

# !DIRECTORY-CHANGE!
cd /app/www/embark/ || exit 1

# db_init
echo -e "\n[""$BLUE JOB""$NC""] Starting migrations - log to embark/logs/migration.log"
pipenv run ./manage.py makemigrations users uploader dashboard reporter | tee -a /app/www/logs/migration.log
pipenv run ./manage.py migrate | tee -a /app/www/logs/migration.log

# collect staticfiles and make accesable for server
echo -e "\n[""$BLUE JOB""$NC""] Collecting static files"
pipenv run ./manage.py collectstatic --no-input
chown www-embark /app/www/ -R
# chown www-embark /app/emba -R
chmod 760 /app/www/media/ -R

echo -e "\n[""$BLUE JOB""$NC""] Starting runapscheduler"
pipenv run ./manage.py runapscheduler | tee -a /app/www/logs/scheduler.log &
sleep 5

echo -e "\n[""$BLUE JOB""$NC""] Starting Apache"
pipenv run ./manage.py runmodwsgi --user www-embark --group sudo \
--host "$BIND_IP" --port="$HTTP_PORT" --limit-request-body "$FILE_SIZE" \
--url-alias /static/ /app/www/static/ \
--url-alias /media/ /app/www/media/ \
--allow-localhost --working-directory /app/www/embark/ --server-root /app/www/httpd80/ \
--include-file /app/www/conf/embark.conf \
--server-name embark.local \
--ssl-certificate /app/www/conf/cert/embark.local --ssl-certificate-key-file /app/www/conf/cert/embark.local.key \
--https-port "$HTTPS_PORT" &
# --enable-debugger --https-only \
sleep 5

echo -e "\n[""$BLUE JOB""$NC""] Starting daphne(ASGI) - log to /embark/logs/daphne.log"
pipenv run daphne --access-log /app/www/logs/daphne.log -e ssl:8000:privateKey=/app/www/conf/cert/embark-ws.local.key:certKey=/app/www/conf/cert/embark-ws.local.crt -b "$BIND_IP" -p 8001 -s embark-ws.local --root-path=/app/www/embark embark.asgi:application &
sleep 5


echo -e "\n""$ORANGE$BOLD""=============================================================""$NC"
echo -e "\n""$ORANGE$BOLD""Server started on http://embark.local""$NC"
echo -e "\n""$ORANGE$BOLD""For SSL you may use https://embark.local (Not recommended for local use)""$NC"
echo -e "\n\n""$GREEN$BOLD""the trusted rootCA.key for the ssl encryption is in /app/cert""$NC"
wait
installer.sh METASEP
#!/bin/bash

# EMBArk - The firmware security scanning environment
#
# Copyright 2020-2022 Siemens Energy AG
# Copyright 2020-2021 Siemens AG
#
# EMBArk comes with ABSOLUTELY NO WARRANTY.
#
# EMBArk is licensed under MIT
#
# Author(s): Michael Messner, Pascal Eckmann
# Contributor(s): Benedikt Kuehne

# Description: Installer for EMBArk

export DEBIAN_FRONTEND=noninteractive

DJANGO_SECRET_KEY=$(python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())')
RANDOM_PW=$(tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w 10 | head -n 1)

RANDOM_SALT=$(tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w 43 | head -n 1)
HASHID_FIELD_LENGTH=7
HASHID_DESCRIPTOR=False
HASHID_DESCRIPTOR=False


SUPER_PW="embark"
SUPER_EMAIL="idk@lol.com"
SUPER_USER="superuser"

# DIR="$(realpath "$(dirname "$0")")"

RED='\033[0;31m'
GREEN='\033[0;32m'
ORANGE='\033[0;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # no color

print_help() {
  echo -e "\\n""$CYAN""USAGE""$NC"
  echo -e "$CYAN-h$NC         Print this help message"
  echo -e "$CYAN-d$NC         EMBArk default installation"
  echo -e "$CYAN-F$NC         Installation of EMBArk for developers"
  echo -e "$CYAN-e$NC         Install EMBA only"
  echo -e "$CYAN-D$NC         Install for Docker deployment"
  echo -e "---------------------------------------------------------------------------"
  echo -e "$CYAN-U$NC         Uninstall EMBArk"
  echo -e "$CYAN-r$NC         Reinstallation of EMBArk with all dependencies"
  echo -e "$RED               ! Both options delete all Database-files as well !""$NC"
}

write_env() {
  echo -e "$ORANGE""$BOLD""Creating a EMBArk configuration file .env""$NC"
  export DATABASE_NAME="embark"
  export DATABASE_USER="embark"
  export DATABASE_PASSWORD="$RANDOM_PW"
  export DATABASE_HOST="127.0.0.1"
  export DATABASE_PORT="3306"
  export MYSQL_PASSWORD="$RANDOM_PW"
  export MYSQL_USER="embark"
  export MYSQL_DATABASE="embark"
  export REDIS_HOST="127.0.0.1"
  export REDIS_PORT="7777"
  export SECRET_KEY="$DJANGO_SECRET_KEY"
  export HASHID_SALT="$RANDOM_SALT"
  export HASHID_FIELD_MIN_LENGTH="$HASHID_FIELD_LENGTH"
  export HASHID_FIELD_ENABLE_HASHID_OBJECT="$HASHID_OBJECT"
  export HASHID_FIELD_ENABLE_DESCRIPTOR="$HASHID_DESCRIPTOR"
  export DJANGO_SUPERUSER_USERNAME="$SUPER_USER"
  export DJANGO_SUPERUSER_EMAIL="$SUPER_EMAIL"
  export DJANGO_SUPERUSER_PASSWORD="$SUPER_PW"
  {
    echo "DATABASE_NAME=$DATABASE_NAME"
    echo "DATABASE_USER=$DATABASE_USER" 
    echo "DATABASE_PASSWORD=$DATABASE_PASSWORD"
    echo "DATABASE_HOST=$DATABASE_HOST"
    echo "DATABASE_PORT=$DATABASE_PORT"
    echo "MYSQL_PASSWORD=$MYSQL_PASSWORD"
    echo "MYSQL_USER=$MYSQL_USER"
    echo "MYSQL_DATABASE=$MYSQL_DATABASE"
    echo "REDIS_HOST=$REDIS_HOST"
    echo "REDIS_PORT=$REDIS_PORT"
    echo "SECRET_KEY=$DJANGO_SECRET_KEY"
    echo "DJANGO_SUPERUSER_USERNAME=$SUPER_USER"
    echo "DJANGO_SUPERUSER_EMAIL=$SUPER_EMAIL"
    echo "DJANGO_SUPERUSER_PASSWORD=$SUPER_PW"
    echo "HASHID_SALT=$RANDOM_SALT"
    echo "HASHID_FIELD_MIN_LENGTH=$HASHID_FIELD_LENGTH"
    echo "HASHID_FIELD_ENABLE_HASHID_OBJECT=$HASHID_OBJECT"
    echo "HASHID_FIELD_ENABLE_DESCRIPTOR=$HASHID_DESCRIPTOR"
    echo "PYTHONPATH=${PYTHONPATH}:${PWD}"
  } > .env
}

install_emba() {
  echo -e "\n$GREEN""$BOLD""Installation of the firmware scanner EMBA on host""$NC"
    git submodule init
    git submodule update
    cd emba || exit 1
    ./installer.sh -d
    cp ./config/emba_updater /etc/cron.daily/
    cd .. || exit 1
  
}

create_ca (){
  # TODO could use some work 
  echo -e "\n$GREEN""$BOLD""Creating SSL Cert""$NC"
  cd cert || exit 1
  # create CA
  openssl genrsa -out rootCA.key 4096
  openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt -subj '/CN=embark.local/O=EMBA/C=US'
  # create server sign requests (csr)
  openssl genrsa -out embark.local.key 2048
  openssl req -new -sha256 -key embark.local.key -out embark.local.csr  -subj '/CN=embark.local/O=EMBA/C=US'
  openssl genrsa -out embark-ws.local.key 2048
  openssl req -new -sha256 -key embark-ws.local.key -out embark-ws.local.csr  -subj '/CN=embark-ws.local/O=EMBA/C=US'
  # signe csr with ca
  openssl x509 -req -in embark.local.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out embark.local.crt -days 10000 -sha256
  openssl x509 -req -in embark-ws.local.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out embark-ws.local.crt -days 10000 -sha256
  cd .. || exit 1
}

dns_resolve(){
  echo -e "\n$GREEN""$BOLD""Install hostnames for local dns-resolve""$NC"
  printf "0.0.0.0     embark.local\n" >>/etc/hosts
}

reset_docker() {
  echo -e "\n$GREEN""$BOLD""Reset EMBArk docker images""$NC"

  docker image ls -a

  docker container stop embark_db
  docker container stop embark_redis
  docker container stop embark_server
  docker container prune -f --filter "label=flag"

  if docker images | grep -qE "^embeddedanalyzer/emba"; then
    echo -e "\n$GREEN""$BOLD""Found EMBA docker environment - removing it""$NC"
    CONTAINER_ID=$(docker images | grep -E "embeddedanalyzer/emba" | awk '{print $3}')
    echo -e "$GREEN""$BOLD""Remove EMBA docker image""$NC"
    docker image rm "$IMAGE_ID" -f
  fi

  if docker images | grep -qE "^embark[[:space:]]*latest"; then
    echo -e "\n$GREEN""$BOLD""Found EMBArk docker environment - removing it""$NC"
    CONTAINER_ID=$(docker container ls -a | grep -E "embark_embark_1" | awk '{print $1}')
    echo -e "$GREEN""$BOLD""Stop EMBArk docker container""$NC"
    docker container stop "$CONTAINER_ID"
    echo -e "$GREEN""$BOLD""Remove EMBArk docker container""$NC"
    docker container rm "$CONTAINER_ID" -f
    echo -e "$GREEN""$BOLD""Remove EMBArk docker image""$NC"
    docker image rm embark:latest -f
  fi

  if docker images | grep -qE "^mysql[[:space:]]*latest"; then
    echo -e "\n$GREEN""$BOLD""Found mysql docker environment - removing it""$NC"
    CONTAINER_ID=$(docker container ls -a | grep -E "embark_db" | awk '{print $1}')
    echo -e "$GREEN""$BOLD""Stop mysql docker container""$NC"
    docker container stop "$CONTAINER_ID"
    echo -e "$GREEN""$BOLD""Remove mysql docker container""$NC"
    docker container rm "$CONTAINER_ID" -f
    echo -e "$GREEN""$BOLD""Remove mysql docker image""$NC"
    docker image rm mysql:latest -f
  fi

  if docker images | grep -qE "^redis[[:space:]]*5"; then
    echo -e "\n$GREEN""$BOLD""Found redis docker environment - removing it""$NC"
    CONTAINER_ID=$(docker container ls -a | grep -E "embark_redis" | awk '{print $1}')
    echo -e "$GREEN""$BOLD""Stop redis docker container""$NC"
    docker container stop "$CONTAINER_ID"
    echo -e "$GREEN""$BOLD""Remove redis docker container""$NC"
    docker container rm "$CONTAINER_ID" -f
    echo -e "$GREEN""$BOLD""Remove redis docker image""$NC"
    docker image rm redis:5 -f
  fi

  #networks

  if docker network ls | grep -E "embark_dev"; then
    echo -e "\n$GREEN""$BOLD""Found EMBArk_dev network - removing it""$NC"
    NET_ID=$(docker network ls | grep -E "embark_dev" | awk '{print $1}')
    echo -e "$GREEN""$BOLD""Remove EMBArk_dev network""$NC"
    docker network rm "$NET_ID" 
  fi

  if docker network ls | grep -E "embark_frontend"; then
    echo -e "\n$GREEN""$BOLD""Found EMBArk_frontend network - removing it""$NC"
    NET_ID=$(docker network ls | grep -E "embark_frontend" | awk '{print $1}')
    echo -e "$GREEN""$BOLD""Remove EMBArk_frontend network""$NC"
    docker network rm "$NET_ID" 
  fi

  if docker network ls | grep -E "embark_backend"; then
    echo -e "\n$GREEN""$BOLD""Found EMBArk_backend network - removing it""$NC"
    NET_ID=$(docker network ls | grep -E "embark_backend" | awk '{print $1}')
    echo -e "$GREEN""$BOLD""Remove EMBArk_backend network""$NC"
    docker network rm "$NET_ID" 
  fi
  
}

install_debs() {
  echo -e "\n$GREEN""$BOLD""Install debian packages for EMBArk installation""$NC"
  apt-get update -y
  if ! command -v git > /dev/null ; then
    apt-get install -y -q git
  fi
  if ! command -v docker > /dev/null ; then
    apt-get install -y -q docker.io
  fi
  if ! command -v docker-compose > /dev/null ; then
    apt-get install -y -q docker-compose
  fi
  # we need the django package on the host for generating the django SECRET_KEY and pip
  apt-get install -y -q python3-django python3-pip python3.9-dev
  # python3-dev
  # mark dir as safe for git
  # git config --global --add safe.directory "$PWD"
}

install_daemon() {
  echo -e "\n$GREEN""$BOLD""Install embark daemon""$NC"
  ln -s /app/embark.service /etc/systemd/system/embark.service
}

install_embark_default() {
  echo -e "\n$GREEN""$BOLD""Installation of the firmware scanning environment EMBArk""$NC"
  
  #debs
  apt-get install -y -q python3-dev default-libmysqlclient-dev build-essential
  
  # install pipenv
  pip3 install pipenv

  #Add user for server
  useradd www-embark -G sudo -c "embark-server-user" -M -r --shell=/usr/sbin/nologin -d /app/www/
  # TODO add if grep /app/emba/emba.sh
  echo 'www-embark ALL=(ALL) NOPASSWD: /app/emba/emba.sh' | EDITOR='tee -a' visudo

  #Add Symlink
  if ! [[ -d /app ]]; then
    ln -s "$PWD" /app || exit 1
  fi

  # daemon
  install_daemon

  #make dirs
  if ! [[ -d ./www ]]; then
    mkdir ./www
    mkdir ./www/media/
    mkdir ./www/active/
    mkdir ./www/emba_logs
    mkdir ./www/static
    mkdir ./www/conf
  fi
  
  #add ssl cert
  create_ca
  ln -s /app/cert /app/www/conf/cert || exit 1

  #add dns name
  dns_resolve

  #install packages
  PIPENV_VENV_IN_PROJECT=1 pipenv install

  # download externals
  if ! [[ -d ./embark/static/external ]]; then
    echo -e "\n$GREEN""$BOLD""Downloading of external files, e.g. jQuery, for the offline usability of EMBArk""$NC"
    mkdir -p ./embark/static/external/{scripts,css}
    wget -O ./embark/static/external/scripts/jquery.js https://code.jquery.com/jquery-3.6.0.min.js
    wget -O ./embark/static/external/scripts/confirm.js https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.js
    wget -O ./embark/static/external/scripts/bootstrap.js https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js
    wget -O ./embark/static/external/scripts/datatable.js https://cdn.datatables.net/v/bs5/dt-1.11.2/datatables.min.js
    wget -O ./embark/static/external/scripts/charts.js https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js
    wget -O ./embark/static/external/css/confirm.css https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.css
    wget -O ./embark/static/external/css/bootstrap.css https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css
    wget -O ./embark/static/external/css/datatable.css https://cdn.datatables.net/v/bs5/dt-1.11.2/datatables.min.css
    find ./embark/static/external/ -type f -exec sed -i '/sourceMappingURL/d' {} \;
  fi

  # write env-vars into ./.env
  write_env

  # download images for container
  docker-compose -f ./docker-compose.yml up --no-start
  docker-compose -f ./docker-compose.yml up &>/dev/null &
  sleep 30
  kill %1

  # activate daemon
  systemctl start embark.service

  echo -e "$GREEN""$BOLD""Ready to use \$sudo ./run-server.sh ""$NC"
  echo -e "$GREEN""$BOLD""Which starts the server on (0.0.0.0) port 80 ""$NC"
}

install_embark_dev(){
  echo -e "\n$GREEN""$BOLD""Building Developent-Enviroment for EMBArk""$NC"
  apt-get install -y -q npm pycodestyle python3-pylint-django python3-dev default-libmysqlclient-dev build-essential pipenv bandit
  npm install -g jshint dockerlinter

  #pipenv
  PIPENV_VENV_IN_PROJECT=1 pipenv install --dev

  # download externals
  if ! [[ -d ./embark/static/external ]]; then
    echo -e "\n$GREEN""$BOLD""Downloading of external files, e.g. jQuery, for the offline usability of EMBArk""$NC"
    mkdir -p ./embark/static/external/{scripts,css}
    wget -O ./embark/static/external/scripts/jquery.js https://code.jquery.com/jquery-3.6.0.min.js
    wget -O ./embark/static/external/scripts/confirm.js https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.js
    wget -O ./embark/static/external/scripts/bootstrap.js https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js
    wget -O ./embark/static/external/scripts/datatable.js https://cdn.datatables.net/v/bs5/dt-1.11.2/datatables.min.js
    wget -O ./embark/static/external/scripts/charts.js https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js
    wget -O ./embark/static/external/css/confirm.css https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.css
    wget -O ./embark/static/external/css/bootstrap.css https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css
    wget -O ./embark/static/external/css/datatable.css https://cdn.datatables.net/v/bs5/dt-1.11.2/datatables.min.css
    find ./embark/static/external/ -type f -exec sed -i '/sourceMappingURL/d' {} \;
  fi

  # write env-vars into ./.env
  write_env

  #Add Symlink
  if ! [[ -d /app ]]; then
    ln -s "$PWD" /app || exit 1
  fi

  # daemon
  # install_daemon

  # download images for container
  docker-compose -f ./docker-compose-dev.yml up --no-start
  docker-compose -f ./docker-compose-dev.yml up &>/dev/null &
  sleep 30
  kill %1

  echo -e "$GREEN""$BOLD""Ready to use \$sudo ./dev-tools/debug-server-start.sh""$NC"
  echo -e "$GREEN""$BOLD""Or use otherwise""$NC"
}

#install as docker-service # TODO
install_embark_docker(){
  echo -e "\n$GREEN""$BOLD""Installing EMBArk as docker-container""$NC"

  echo -e "\n$GREEN""$BOLD""Downloading of external files, e.g. jQuery, for the offline usability of EMBArk""$NC"
  mkdir -p ./embark/static/external/{scripts,css}
  wget -O ./embark/static/external/scripts/jquery.js https://code.jquery.com/jquery-3.6.0.min.js
  wget -O ./embark/static/external/scripts/confirm.js https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.js
  wget -O ./embark/static/external/scripts/bootstrap.js https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js
  wget -O ./embark/static/external/scripts/datatable.js https://cdn.datatables.net/v/bs5/dt-1.11.2/datatables.min.js
  wget -O ./embark/static/external/scripts/charts.js https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js
  wget -O ./embark/static/external/css/confirm.css https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.css
  wget -O ./embark/static/external/css/bootstrap.css https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css
  wget -O ./embark/static/external/css/datatable.css https://cdn.datatables.net/v/bs5/dt-1.11.2/datatables.min.css
  find ./embark/static/external/ -type f -exec sed -i '/sourceMappingURL/d' {} \;

  # generating dynamic authentication for backend
  # for MYSQL root pwd check the logs of the container
  echo -e "$ORANGE""$BOLD""Creating a container EMBArk configuration file .env""$NC"
  export DATABASE_NAME="embark"
  export DATABASE_USER="embark"
  export DATABASE_PASSWORD="$RANDOM_PW"
  export DATABASE_HOST="172.23.0.5"
  export DATABASE_PORT="3306"
  export MYSQL_PASSWORD="$RANDOM_PW"
  export MYSQL_USER="embark"
  export MYSQL_DATABASE="embark"
  export REDIS_HOST="172.23.0.8"
  export REDIS_PORT="7777"
  export SECRET_KEY="$DJANGO_SECRET_KEY"
  # this is for pipenv/django # TODO change/lock after deploy
  {
    echo "DATABASE_NAME=$DATABASE_NAME"
    echo "DATABASE_USER=$DATABASE_USER" 
    echo "DATABASE_PASSWORD=$DATABASE_PASSWORD"
    echo "DATABASE_HOST=$DATABASE_HOST"
    echo "DATABASE_PORT=$DATABASE_PORT"
    echo "MYSQL_PASSWORD=$MYSQL_PASSWORD"
    echo "MYSQL_USER=$MYSQL_USER"
    echo "MYSQL_DATABASE=$MYSQL_DATABASE"
    echo "REDIS_HOST=$REDIS_HOST"
    echo "REDIS_PORT=$REDIS_PORT"
    echo "SECRET_KEY=$DJANGO_SECRET_KEY"
    echo "PYTHONPATH=${PYTHONPATH}:${PWD}:${PWD}/embark/"
  } > .env

  # setup dbs-container and detach build could be skipt
  echo -e "\n$GREEN""$BOLD""Building EMBArk docker images""$NC"
  docker-compose -f ./docker-compose-docker.yml build
  DB_RETURN=$?
  if [[ $DB_RETURN -eq 0 ]] ; then
    echo -e "$GREEN""$BOLD""Finished building EMBArk docker images""$NC"
  else
    echo -e "$ORANGE""$BOLD""Failed building EMBArk docker images""$NC"
  fi

  echo -e "\n$GREEN""$BOLD""Starting EMBArk docker images""$NC"
  docker-compose -f ./docker-compose-docker.yml up -d
  DS_RETURN=$?
  if [[ $DS_RETURN -eq 0 ]] ; then
    echo -e "$GREEN""$BOLD""Finished starting EMBArk""$NC"
  else
    echo -e "$ORANGE""$BOLD""Failed starting EMBArk""$NC"
  fi

  echo -e "$GREEN""$BOLD""Testing EMBArk installation""$NC"
  # need to wait a few seconds until everyting is up and running
  sleep 5
  if ! curl -XGET 'http://0.0.0.0:80' | grep -q embark; then
    echo -e "$ORANGE""$BOLD""Failed installing EMBArk - check the output from the installation process""$NC"
  fi

  echo -e "$GREEN""$BOLD""Server ready to use""$NC"
  echo -e "$GREEN""EMBArk is on (0.0.0.0) port 80 ""$NC"
}

uninstall (){
  echo -e "$ORANGE""$BOLD""Deleting Configuration and reseting""$NC"

  #1 delete symlink
  echo -e "$ORANGE""$BOLD""Delete Symlink?""$NC"
  rm /app

  #2 delete www and upload-dir
  echo -e "$ORANGE""$BOLD""Delete Directories""$NC"
  rm -R ./www
  rm -R ./uploadedFirmwareImages

  #3 delete user www-embark and reset visudo
  echo -e "$ORANGE""$BOLD""Delete user""$NC"
  # sed -i 's/www\-embark\ ALL\=\(ALL\)\ NOPASSWD\:\ \/app\/emba\/emba.sh//g' /etc/sudoers #TODO doesnt work yet
  userdel www-embark

  #4 delete venv
  echo -e "$ORANGE""$BOLD""Delete Venv""$NC"
  rm -R ./.venv

  #5 delete .env
  echo -e "$ORANGE""$BOLD""Delete env""$NC"
  rm -R ./.env

  #6 delete shared volumes and migrations
  echo -e "$ORANGE""$BOLD""Delete Database-files""$NC"
  rm -R ./embark_db
  find . -path "*/migrations/*.py" -not -name "__init__.py" -delete
  find . -path "*/migrations/*.pyc"  -delete

  #7 delete all docker interfaces and containers + images
  reset_docker
  echo -e "$ORANGE""$BOLD""Consider running \$docker system prune""$NC"

  #8 delete/uninstall EMBA
  echo -e "$ORANGE""$BOLD""Delete EMBA?""$NC"
  docker network rm emba_runs
  git submodule foreach git reset --hard
  git submodule foreach deinit --all

  #9 stop daemon
  systemctl stop embark.service
  systemctl disable embark.service
  git checkout HEAD -- embark.service
  systemctl daemon-reload

  #10 reset ownership etc
  # TODO delete the dns resolve

  #11 remove server-certs
  rm -rf ./cert
  git checkout HEAD -- cert

  #final
  git reset
}

echo -e "\\n$ORANGE""$BOLD""EMBArk Installer""$NC\\n""$BOLD=================================================================$NC"
echo -e "$ORANGE""$BOLD""WARNING: This script can harm your environment!""$NC\n"

if [ "$#" -ne 1 ]; then
  echo -e "$RED""$BOLD""Invalid number of arguments""$NC"
  print_help
  exit 1
fi

while getopts eFUrdDh OPT ; do
  case $OPT in
    e)
      export EMBA_ONLY=1
      echo -e "$GREEN""$BOLD""Install only emba""$NC"
      ;;
    F)
      export DEV=1
      echo -e "$GREEN""$BOLD""Building Development-Enviroment""$NC"
      ;;
    U)
      export UNINSTALL=1
      echo -e "$GREEN""$BOLD""Uninstall EMBArk""$NC"
      ;;
    r)
      export UNINSTALL=1
      export REFORCE=1
      echo -e "$GREEN""$BOLD""Install all dependecies including docker cleanup""$NC"
      ;;
    d)
      export DEFAULT=1
      echo -e "$GREEN""$BOLD""Default installation of EMBArk""$NC"
      ;;
    D)
      export DOCKER=1
      echo -e "$GREEN""$BOLD""Install all dependecies for EMBArk-docker-container""$NC"
      ;;
    h)
      print_help
      exit 0
      ;;
    *)
      echo -e "$RED""$BOLD""Invalid option""$NC"
      print_help
      exit 1
      ;;
  esac
done

if ! [[ $EUID -eq 0 ]] && [[ $LIST_DEP -eq 0 ]] ; then
  echo -e "\\n$RED""Run EMBArk installation script with root permissions!""$NC\\n"
  print_help
  exit 1
fi

if [[ $REFORCE -eq 1 ]] && [[ $UNINSTALL -eq 1 ]]; then
  uninstall
elif [[ $UNINSTALL -eq 1 ]]; then
  uninstall
  exit 0
fi

install_debs
install_emba

if [[ $DEFAULT -eq 1 ]]; then
  install_embark_default
elif [[ $DEV -eq 1 ]]; then
  install_embark_dev
elif [[ $DOCKER -eq 1 ]]; then
  install_embark_docker
fi

exit 0

embark.service METASEP
[Unit]
Description=EMBArk supervisor daemon

[Service]
Type=simple
ExecStart=/app/supervisor.sh

[Install]
WantedBy=multi-user.target
docker-compose.yml METASEP
version: "3"
# https://docs.docker.com/compose/compose-file/compose-file-v3/#deploy
services:

  embark_db:
    container_name: embark_db
    image: mysql:latest
    labels:
      flag: "deploy_EMBArk_server_mysql_container"
    env_file:
      - .env
    environment:
      - MYSQL_RANDOM_ROOT_PASSWORD=yes
    expose:
      - "3306"
    ports:
      - "3306:3306"
    restart: always
    volumes:
      - ./embark_db:/var/lib/mysql
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    networks:
      embark_backend:
        ipv4_address: "172.22.0.5"

  embark_redis:
    container_name: embark_redis
    image: "redis:5"
    labels:
      flag: "deploy_EMBArk_server_redis_container"
    env_file:
      - ./.env
    command: --port 7777
    expose:
      - "7777"
    ports:
      - "7777:7777"
    restart: always
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    networks:
      embark_backend:
        ipv4_address: "172.22.0.8"
    logging:
      driver: "local"


networks:
  embark_backend:
    name: embark_backend
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: embark_backend
    ipam:
      config:
        - subnet: "172.22.0.0/16"


docker-compose-docker.yml METASEP
version: "3"

services:
  embark_db:
    image: mysql:latest
    container_name: embark_db
    #TODO add flag
    restart: always
    expose:
      - "3306"
    env_file:
      - .env
    environment:
      - MYSQL_RANDOM_ROOT_PASSWORD=yes
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    networks:
      - embark_backend

  embark_server:
    image: embark:latest
    container_name: embark_server
    build: .
    restart: always
    privileged: true
    env_file:
      - .env
    volumes:
        - /var/run/docker.sock:/var/run/docker.sock 
        - ./emba/:/app/emba/
        - /etc/timezone:/etc/timezone:ro
        - /etc/localtime:/etc/localtime:ro
    ports:
      - "80:80"
      - "8001:8001"
      - "443:443"
    networks:
      - embark_backend
    environment:
      - TODO
    command: './entrypoint.sh'
    depends_on:
        - embark_db
        - embark_redis

  embark_redis:
    image: "redis:5"
    container_name: embark_redis
    command: --port 7777
    env_file:
      - .env
    expose:
      - "7777"
    restart: always
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    networks:
      - embark_backend

networks:
  embark_backend_docker:
    name: embark_backend_docker
    driver: bridge
    ipam:
      config:
        - subnet: "172.23.0.0/16"



docker-compose-dev.yml METASEP
version: "3"

services:

  embark_db_dev:
    container_name: embark_db_dev
    image: mysql:latest
    labels:
      flag: "dev_EMBArk_server_mysql_container"
    env_file:
      - .env
    environment:
      - MYSQL_RANDOM_ROOT_PASSWORD=yes
    expose:
      - "3306"
    ports:
      - "3306:3306"
    restart: always
    volumes:
      #- TODO share conf file for db
      # ./embark_mysql.conf.d:/etc/mysql/conf.d
      - ./embark_db:/var/lib/mysql
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    networks:
      embark_dev:
        ipv4_address: "172.21.0.5"

  embark_redis_dev:
    container_name: embark_redis_dev
    image: redis:latest
    command: --port 7777
    expose:
      - "7777"
    ports:
      - "7777:7777"
    restart: always
    labels:
      flag: "dev_EMBArk_server_redis_container"
    env_file:
      - .env
    volumes:
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    networks:
      embark_dev:
        ipv4_address: "172.21.0.8"
    logging:
      driver: "local"

networks:
  embark_dev:
    name: embark_dev
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: embark_dev
    ipam:
      config:
        - subnet: "172.21.0.0/16"


SECURITY.md METASEP
# Security Policy
<!---
## Supported Versions

| Version | Supported          |
| ------- | ------------------ |
| 5.1.x   | :white_check_mark: |
| 5.0.x   | :x:                |
| 4.0.x   | :white_check_mark: |
| < 4.0   | :x:                |
-->

## Reporting a Vulnerability

If there is a problem within the code or the dependencies feel free to contact me.

README.md METASEP
# EMBArk - The firmware security scanning environment <br>


<p align="center" style="margin:25px;">
  <img src="./embark/static/content/images/header.png" alt="" width="100%"/>
</p>
<p align="center">
  <a href="https://github.com/e-m-b-a/embark/blob/master/LICENSE"><img src="https://img.shields.io/github/license/e-m-b-a/embark?label=License"></a>
  <img src="https://img.shields.io/badge/python-3.7+-blue.svg">
  <a href="https://github.com/e-m-b-a/embark/stargazers"><img src="https://img.shields.io/github/stars/e-m-b-a/embark?label=Stars"></a>
  <a href="https://github.com/e-m-b-a/embark/network/members"><img src="https://img.shields.io/github/forks/e-m-b-a/embark?label=Forks"></a>
  <a href="https://github.com/e-m-b-a/embark/graphs/contributors"><img src="https://img.shields.io/github/contributors/e-m-b-a/embark?color=9ea"></a>
  <a href="https://twitter.com/intent/tweet?text=Check%20out%20EMBArk%20-%20The%20Firmware%20security%20scanning%20environment!%20https://github.com/e-m-b-a/embark"><img src="https://img.shields.io/twitter/url.svg?style=social&url=https%3A%2F%2Fgithub.com%2Fe-m-b-a%2Fembark"></a>
</p>

## About

*EMBArk* is the web based enterprise interface for the firmware security scanner *EMBA*. It is developed to provide the firmware security analyzer *[EMBA](https://github.com/e-m-b-a/emba)* as a containerized service and to ease accessibility to the firmware scanning backend *EMBA* regardless of system and operating system.

Furthermore *EMBArk* improves the data provision by aggregating the various scanning results in an [aggregated management dashboard](https://github.com/e-m-b-a/embark/wiki/Web-interface#main-dashboard).

[![Watch EMBArk](https://raw.githubusercontent.com/wiki/e-m-b-a/embark/static/images/EMBArk-YouTube.png)](https://youtu.be/qSHuPWbfhmI "Watch EMBArk")


## Automated setup (on Kali Linux)
1. Checkout the repository (e.g. `git clone https://github.com/e-m-b-a/embark.git`)
2. Change directory to root of the repository i.e `cd embark`
3. Run `sudo ./installer.sh -d ` to run the default installation.
4. Further details are available in the wiki (See [installation](https://github.com/e-m-b-a/embark/wiki/Installation))
5. If you are running into issues or need to install *EMBArk* manually please take a look at the wiki (See [build instructions](https://github.com/e-m-b-a/embark/wiki/Build-EMBArk))

*EMBArk* is using the firmware security scanner *EMBA* as backend. You can take a look at the [*EMBA*](https://github.com/e-m-b-a/emba) repository and get more [information about usage of *EMBA* in the wiki](https://github.com/e-m-b-a/emba/wiki/Usage). Additionally you should check the [*EMBArk* wiki](https://github.com/e-m-b-a/embark/wiki).

## Start Server
To start the EMBArk-Server simply run `$ sudo ./run-server.sh`.
This starts the http-Server on 0.0.0.0:80

## Developer
For developers we recommend simply using: `sudo ./installer.sh -F ` and the `./dev-tools/debug-server-start.sh` script.

## Get involved
The IoT is growing, the development is ongoing, and there are many new features that we want to add.
We welcome [pull requests](https://github.com/e-m-b-a/embark/pulls) and bug reports or feature requests via [issues](https://github.com/e-m-b-a/embark/issues) on GitHub.

## Acknowledgement
This project was originally initiated by [Siemens Energy](https://www.siemens-energy.com/) as [AMOS project](https://oss.cs.fau.de/teaching/the-amos-project/) in cooperation with the [FAU](https://oss.cs.fau.de/).

See also the original [*EMBArk* AMOS project](https://github.com/amosproj/amos2021ss01-emba-service) and [AMOS](https://github.com/amosproj).

Pipfile.lock METASEP
{
    "_meta": {
        "hash": {
            "sha256": "f84736456b1e847d98c2e2c98c49cd2c587a69473d27f6735e6f2f9de75dc0df"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.9"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.python.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "aioredis": {
            "hashes": [
                "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a",
                "sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3"
            ],
            "version": "==1.3.1"
        },
        "apscheduler": {
            "hashes": [
                "sha256:65e6574b6395498d371d045f2a8a7e4f7d50c6ad21ef7313d15b1c7cf20df1e3",
                "sha256:ddc25a0ddd899de44d7f451f4375fb971887e65af51e41e5dcf681f59b8b2c9a"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
            "version": "==3.9.1"
        },
        "asgiref": {
            "hashes": [
                "sha256:45a429524fba18aba9d512498b19d220c4d628e75b40cf5c627524dbaebc5cc1",
                "sha256:fddeea3c53fa99d0cdb613c3941cc6e52d822491fc2753fba25768fb5bf4e865"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==3.5.1"
        },
        "async-timeout": {
            "hashes": [
                "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
                "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==4.0.2"
        },
        "attrs": {
            "hashes": [
                "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4",
                "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==21.4.0"
        },
        "autobahn": {
            "hashes": [
                "sha256:57b7acf228d50d83cf327372b889e2a168a869275b26e17917ed0b4cf4d823a6"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==22.4.2"
        },
        "automat": {
            "hashes": [
                "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33",
                "sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111"
            ],
            "version": "==20.2.0"
        },
        "cffi": {
            "hashes": [
                "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3",
                "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2",
                "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636",
                "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20",
                "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728",
                "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27",
                "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66",
                "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443",
                "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0",
                "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7",
                "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39",
                "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605",
                "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a",
                "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37",
                "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029",
                "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139",
                "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc",
                "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df",
                "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14",
                "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880",
                "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2",
                "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a",
                "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e",
                "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474",
                "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024",
                "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8",
                "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0",
                "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e",
                "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a",
                "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e",
                "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032",
                "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6",
                "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e",
                "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b",
                "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e",
                "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954",
                "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962",
                "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c",
                "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4",
                "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55",
                "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962",
                "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023",
                "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c",
                "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6",
                "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8",
                "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382",
                "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7",
                "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc",
                "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997",
                "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"
            ],
            "version": "==1.15.0"
        },
        "channels": {
            "hashes": [
                "sha256:0ff0422b4224d10efac76e451575517f155fe7c97d369b5973b116f22eeaf86c",
                "sha256:fdd9a94987a23d8d7ebd97498ed8b8cc83163f37e53fc6c85098aba7a3bb8b75"
            ],
            "index": "pypi",
            "version": "==3.0.4"
        },
        "channels-redis": {
            "hashes": [
                "sha256:5dffd4cc16174125bd4043fc8fe7462ca7403cf801d59a9fa7410ed101fa6a57",
                "sha256:6e4565b7c11c6bcde5d48556cb83bd043779697ff03811867d2f895aa6170d56"
            ],
            "index": "pypi",
            "version": "==3.4.0"
        },
        "constantly": {
            "hashes": [
                "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35",
                "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d"
            ],
            "version": "==15.1.0"
        },
        "cryptography": {
            "hashes": [
                "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804",
                "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178",
                "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717",
                "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982",
                "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004",
                "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe",
                "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452",
                "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336",
                "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4",
                "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15",
                "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d",
                "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c",
                "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0",
                "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06",
                "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9",
                "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1",
                "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023",
                "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de",
                "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f",
                "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181",
                "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e",
                "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==37.0.2"
        },
        "daphne": {
            "hashes": [
                "sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f",
                "sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393"
            ],
            "index": "pypi",
            "version": "==3.0.2"
        },
        "django": {
            "hashes": [
                "sha256:07c8638e7a7f548dc0acaaa7825d84b7bd42b10e8d22268b3d572946f1e9b687",
                "sha256:4e8177858524417563cc0430f29ea249946d831eacb0068a1455686587df40b5"
            ],
            "index": "pypi",
            "version": "==4.0.4"
        },
        "django-apscheduler": {
            "hashes": [
                "sha256:60ced2c07b531b0477076dfef2d6de4790a983a4bbd75fae5e387ac360e9c8fd",
                "sha256:d30a05be98e01c12aa50d0a7afd8d0fd529c874d1c75af20891b6417d2f36475"
            ],
            "index": "pypi",
            "version": "==0.6.2"
        },
        "django-dotenv": {
            "hashes": [
                "sha256:3812bb0f4876cf31f902aad140f0645e120e51ee30eb7c40c22050f58a0e4adb",
                "sha256:a9b1b40a70bd321acd231926acedb9bd2c5e873e33a1873b34a7276d196a765e"
            ],
            "index": "pypi",
            "version": "==1.4.2"
        },
        "django-hashid-field": {
            "hashes": [
                "sha256:72a24577241990e74e00f5b5a2158e95e71d547935253e62e9c32acbd4d0319c",
                "sha256:f67487ad86d9cd432f7dfcccb494732951a83ba8f4c6926a3d877a147c2cce27"
            ],
            "index": "pypi",
            "version": "==3.3.4"
        },
        "hashids": {
            "hashes": [
                "sha256:6c3dc775e65efc2ce2c157a65acb776d634cb814598f406469abef00ae3f635c",
                "sha256:8bddd1acba501bfc9306e7e5a99a1667f4f2cacdc20cbd70bcc5ddfa5147c94c"
            ],
            "markers": "python_version >= '2.7'",
            "version": "==1.3.1"
        },
        "hiredis": {
            "hashes": [
                "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e",
                "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27",
                "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163",
                "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc",
                "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26",
                "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e",
                "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579",
                "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a",
                "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048",
                "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87",
                "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63",
                "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54",
                "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05",
                "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb",
                "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea",
                "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5",
                "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e",
                "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc",
                "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99",
                "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a",
                "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581",
                "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426",
                "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db",
                "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a",
                "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a",
                "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d",
                "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443",
                "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79",
                "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d",
                "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9",
                "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d",
                "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485",
                "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5",
                "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048",
                "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0",
                "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6",
                "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41",
                "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298",
                "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce",
                "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
                "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==2.0.0"
        },
        "hyperlink": {
            "hashes": [
                "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b",
                "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"
            ],
            "version": "==21.0.0"
        },
        "idna": {
            "hashes": [
                "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
                "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
            ],
            "version": "==3.3"
        },
        "incremental": {
            "hashes": [
                "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57",
                "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"
            ],
            "version": "==21.3.0"
        },
        "inotify-simple": {
            "hashes": [
                "sha256:8440ffe49c4ae81a8df57c1ae1eb4b6bfa7acb830099bfb3e305b383005cc128"
            ],
            "index": "pypi",
            "version": "==1.3.5"
        },
        "mod-wsgi-httpd": {
            "hashes": [
                "sha256:4ab310a26dcad14246a66b0440c61a2026fedb9e9face8f2989955881907e1f2"
            ],
            "version": "==2.4.48.1"
        },
        "mod-wsgi-standalone": {
            "hashes": [
                "sha256:23e9ecfba3ea694c0cbb382b72c64aa11cd1e6e4c39f4161e52e724f0fe9854b"
            ],
            "index": "pypi",
            "version": "==4.9.1"
        },
        "msgpack": {
            "hashes": [
                "sha256:0d8c332f53ffff01953ad25131272506500b14750c1d0ce8614b17d098252fbc",
                "sha256:1c58cdec1cb5fcea8c2f1771d7b5fec79307d056874f746690bd2bdd609ab147",
                "sha256:2c3ca57c96c8e69c1a0d2926a6acf2d9a522b41dc4253a8945c4c6cd4981a4e3",
                "sha256:2f30dd0dc4dfe6231ad253b6f9f7128ac3202ae49edd3f10d311adc358772dba",
                "sha256:2f97c0f35b3b096a330bb4a1a9247d0bd7e1f3a2eba7ab69795501504b1c2c39",
                "sha256:36a64a10b16c2ab31dcd5f32d9787ed41fe68ab23dd66957ca2826c7f10d0b85",
                "sha256:3d875631ecab42f65f9dce6f55ce6d736696ced240f2634633188de2f5f21af9",
                "sha256:40fb89b4625d12d6027a19f4df18a4de5c64f6f3314325049f219683e07e678a",
                "sha256:47d733a15ade190540c703de209ffbc42a3367600421b62ac0c09fde594da6ec",
                "sha256:494471d65b25a8751d19c83f1a482fd411d7ca7a3b9e17d25980a74075ba0e88",
                "sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e",
                "sha256:6eef0cf8db3857b2b556213d97dd82de76e28a6524853a9beb3264983391dc1a",
                "sha256:6f4c22717c74d44bcd7af353024ce71c6b55346dad5e2cc1ddc17ce8c4507c6b",
                "sha256:73a80bd6eb6bcb338c1ec0da273f87420829c266379c8c82fa14c23fb586cfa1",
                "sha256:89908aea5f46ee1474cc37fbc146677f8529ac99201bc2faf4ef8edc023c2bf3",
                "sha256:8a3a5c4b16e9d0edb823fe54b59b5660cc8d4782d7bf2c214cb4b91a1940a8ef",
                "sha256:96acc674bb9c9be63fa8b6dabc3248fdc575c4adc005c440ad02f87ca7edd079",
                "sha256:973ad69fd7e31159eae8f580f3f707b718b61141838321c6fa4d891c4a2cca52",
                "sha256:9b6f2d714c506e79cbead331de9aae6837c8dd36190d02da74cb409b36162e8a",
                "sha256:9c0903bd93cbd34653dd63bbfcb99d7539c372795201f39d16fdfde4418de43a",
                "sha256:9fce00156e79af37bb6db4e7587b30d11e7ac6a02cb5bac387f023808cd7d7f4",
                "sha256:a598d0685e4ae07a0672b59792d2cc767d09d7a7f39fd9bd37ff84e060b1a996",
                "sha256:b0a792c091bac433dfe0a70ac17fc2087d4595ab835b47b89defc8bbabcf5c73",
                "sha256:bb87f23ae7d14b7b3c21009c4b1705ec107cb21ee71975992f6aca571fb4a42a",
                "sha256:bf1e6bfed4860d72106f4e0a1ab519546982b45689937b40257cfd820650b920",
                "sha256:c1ba333b4024c17c7591f0f372e2daa3c31db495a9b2af3cf664aef3c14354f7",
                "sha256:c2140cf7a3ec475ef0938edb6eb363fa704159e0bf71dde15d953bacc1cf9d7d",
                "sha256:c7e03b06f2982aa98d4ddd082a210c3db200471da523f9ac197f2828e80e7770",
                "sha256:d02cea2252abc3756b2ac31f781f7a98e89ff9759b2e7450a1c7a0d13302ff50",
                "sha256:da24375ab4c50e5b7486c115a3198d207954fe10aaa5708f7b65105df09109b2",
                "sha256:e4c309a68cb5d6bbd0c50d5c71a25ae81f268c2dc675c6f4ea8ab2feec2ac4e2",
                "sha256:f01b26c2290cbd74316990ba84a14ac3d599af9cebefc543d241a66e785cf17d",
                "sha256:f201d34dc89342fabb2a10ed7c9a9aaaed9b7af0f16a5923f1ae562b31258dea",
                "sha256:f74da1e5fcf20ade12c6bf1baa17a2dc3604958922de8dc83cbe3eff22e8b611"
            ],
            "index": "pypi",
            "version": "==1.0.3"
        },
        "mysqlclient": {
            "hashes": [
                "sha256:02c8826e6add9b20f4cb12dcf016485f7b1d6e30356a1204d05431867a1b3947",
                "sha256:2c8410f54492a3d2488a6a53e2d85b7e016751a1e7d116e7aea9c763f59f5e8c",
                "sha256:973235686f1b720536d417bf0a0d39b4ab3d5086b2b6ad5e6752393428c02b12",
                "sha256:b62d23c11c516cedb887377c8807628c1c65d57593b57853186a6ee18b0c6a5b",
                "sha256:e6279263d5a9feca3e0edbc2b2a52c057375bf301d47da2089c075ff76331d14"
            ],
            "index": "pypi",
            "version": "==2.1.0"
        },
        "psutil": {
            "hashes": [
                "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5",
                "sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a",
                "sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4",
                "sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841",
                "sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d",
                "sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d",
                "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0",
                "sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845",
                "sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf",
                "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b",
                "sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07",
                "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618",
                "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2",
                "sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd",
                "sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666",
                "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce",
                "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3",
                "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d",
                "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25",
                "sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492",
                "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b",
                "sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d",
                "sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2",
                "sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203",
                "sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2",
                "sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94",
                "sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9",
                "sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64",
                "sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56",
                "sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3",
                "sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c",
                "sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3"
            ],
            "index": "pypi",
            "version": "==5.9.0"
        },
        "pyasn1": {
            "hashes": [
                "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
                "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
                "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
                "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
                "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
                "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
                "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
                "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
                "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
                "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
                "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
                "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
                "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
            ],
            "version": "==0.4.8"
        },
        "pyasn1-modules": {
            "hashes": [
                "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
                "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
                "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
                "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
                "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
                "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
                "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
                "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
                "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
                "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
                "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
                "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
                "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"
            ],
            "version": "==0.2.8"
        },
        "pycparser": {
            "hashes": [
                "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
                "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
            ],
            "version": "==2.21"
        },
        "pyopenssl": {
            "hashes": [
                "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf",
                "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0"
            ],
            "version": "==22.0.0"
        },
        "pytz": {
            "hashes": [
                "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
                "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
            ],
            "version": "==2022.1"
        },
        "pytz-deprecation-shim": {
            "hashes": [
                "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6",
                "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
            "version": "==0.1.0.post0"
        },
        "rx": {
            "hashes": [
                "sha256:922c5f4edb3aa1beaa47bf61d65d5380011ff6adcd527f26377d05cb73ed8ec8",
                "sha256:b657ca2b45aa485da2f7dcfd09fac2e554f7ac51ff3c2f8f2ff962ecd963d91c"
            ],
            "index": "pypi",
            "version": "==3.2.0"
        },
        "service-identity": {
            "hashes": [
                "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34",
                "sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db"
            ],
            "version": "==21.1.0"
        },
        "setuptools": {
            "hashes": [
                "sha256:5534570b9980fc650d45c62877ff603c7aaaf24893371708736cc016bd221c3c",
                "sha256:ca6ba73b7fd5f734ae70ece8c4c1f7062b07f3352f6428f6277e27c8f5c64237"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==62.2.0"
        },
        "six": {
            "hashes": [
                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
            "version": "==1.16.0"
        },
        "sqlparse": {
            "hashes": [
                "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae",
                "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"
            ],
            "markers": "python_version >= '3.5'",
            "version": "==0.4.2"
        },
        "twisted": {
            "extras": [
                "tls"
            ],
            "hashes": [
                "sha256:a047990f57dfae1e0bd2b7df2526d4f16dcdc843774dc108b78c52f2a5f13680",
                "sha256:f9f7a91f94932477a9fc3b169d57f54f96c6e74a23d78d9ce54039a7f48928a2"
            ],
            "markers": "python_full_version >= '3.6.7'",
            "version": "==22.4.0"
        },
        "txaio": {
            "hashes": [
                "sha256:2e4582b70f04b2345908254684a984206c0d9b50e3074a24a4c55aba21d24d01",
                "sha256:41223af4a9d5726e645a8ee82480f413e5e300dd257db94bc38ae12ea48fb2e5"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==22.2.1"
        },
        "typing-extensions": {
            "hashes": [
                "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708",
                "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==4.2.0"
        },
        "tzdata": {
            "hashes": [
                "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9",
                "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==2022.1"
        },
        "tzlocal": {
            "hashes": [
                "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745",
                "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==4.2"
        },
        "zope.interface": {
            "hashes": [
                "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192",
                "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702",
                "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09",
                "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4",
                "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a",
                "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3",
                "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf",
                "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c",
                "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d",
                "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78",
                "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83",
                "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531",
                "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46",
                "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021",
                "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94",
                "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc",
                "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63",
                "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54",
                "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117",
                "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25",
                "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05",
                "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e",
                "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1",
                "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004",
                "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2",
                "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e",
                "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f",
                "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f",
                "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120",
                "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f",
                "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1",
                "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9",
                "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e",
                "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7",
                "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8",
                "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b",
                "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155",
                "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7",
                "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c",
                "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325",
                "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d",
                "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb",
                "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e",
                "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959",
                "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7",
                "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920",
                "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e",
                "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48",
                "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8",
                "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
                "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==5.4.0"
        }
    },
    "develop": {
        "astroid": {
            "hashes": [
                "sha256:14ffbb4f6aa2cf474a0834014005487f7ecd8924996083ab411e7fa0b508ce0b",
                "sha256:f4e4ec5294c4b07ac38bab9ca5ddd3914d4bf46f9006eb5c0ae755755061044e"
            ],
            "markers": "python_full_version >= '3.6.2'",
            "version": "==2.11.5"
        },
        "click": {
            "hashes": [
                "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
                "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==8.1.3"
        },
        "colorama": {
            "hashes": [
                "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
                "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==0.4.4"
        },
        "dill": {
            "hashes": [
                "sha256:7e40e4a70304fd9ceab3535d36e58791d9c4a776b38ec7f7ec9afc8d3dca4d4f",
                "sha256:9f9734205146b2b353ab3fec9af0070237b6ddae78452af83d2fca84d739e675"
            ],
            "markers": "python_version >= '2.7' and python_version != '3.0'",
            "version": "==0.3.4"
        },
        "djlint": {
            "hashes": [
                "sha256:1477717ccb4f4789f38c86e7dc9194b8eb5951a5b1804fee8945592a3e851554",
                "sha256:9ccd46ea91f2881c6caac595d4267c5c9c4ecc47024df15e665cc5a6f9c77543"
            ],
            "index": "pypi",
            "version": "==1.0.3"
        },
        "importlib-metadata": {
            "hashes": [
                "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6",
                "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==4.11.3"
        },
        "isort": {
            "hashes": [
                "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7",
                "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"
            ],
            "markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
            "version": "==5.10.1"
        },
        "lazy-object-proxy": {
            "hashes": [
                "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7",
                "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a",
                "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c",
                "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc",
                "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f",
                "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09",
                "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442",
                "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e",
                "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029",
                "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61",
                "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb",
                "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0",
                "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35",
                "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42",
                "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1",
                "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad",
                "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443",
                "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd",
                "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9",
                "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148",
                "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38",
                "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55",
                "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36",
                "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a",
                "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b",
                "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44",
                "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6",
                "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69",
                "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4",
                "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84",
                "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de",
                "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28",
                "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c",
                "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1",
                "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8",
                "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b",
                "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==1.7.1"
        },
        "mccabe": {
            "hashes": [
                "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
                "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==0.7.0"
        },
        "pathspec": {
            "hashes": [
                "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
                "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
            ],
            "version": "==0.9.0"
        },
        "platformdirs": {
            "hashes": [
                "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788",
                "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==2.5.2"
        },
        "pycodestyle": {
            "hashes": [
                "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20",
                "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"
            ],
            "index": "pypi",
            "version": "==2.8.0"
        },
        "pylint": {
            "hashes": [
                "sha256:ced8968c3b699df0615e2a709554dec3ddac2f5cd06efadb69554a69eeca364a",
                "sha256:f87e863a0b08f64b5230e7e779bcb75276346995737b2c0dc2793070487b1ff6"
            ],
            "markers": "python_full_version >= '3.6.2'",
            "version": "==2.13.8"
        },
        "pylint-django": {
            "hashes": [
                "sha256:0ac090d106c62fe33782a1d01bda1610b761bb1c9bf5035ced9d5f23a13d8591",
                "sha256:56b12b6adf56d548412445bd35483034394a1a94901c3f8571980a13882299d5"
            ],
            "index": "pypi",
            "version": "==2.5.3"
        },
        "pylint-plugin-utils": {
            "hashes": [
                "sha256:b3d43e85ab74c4f48bb46ae4ce771e39c3a20f8b3d56982ab17aa73b4f98d535",
                "sha256:ce48bc0516ae9415dd5c752c940dfe601b18fe0f48aa249f2386adfa95a004dd"
            ],
            "markers": "python_full_version >= '3.6.2'",
            "version": "==0.7"
        },
        "pyyaml": {
            "hashes": [
                "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
                "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
                "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
                "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
                "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
                "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
                "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
                "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
                "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
                "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
                "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
                "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
                "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
                "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
                "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
                "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
                "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
                "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
                "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
                "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
                "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
                "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
                "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
                "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
                "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
                "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
                "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
                "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
                "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
                "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
                "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
                "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
                "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==6.0"
        },
        "regex": {
            "hashes": [
                "sha256:02543d6d5c32d361b7cc468079ba4cddaaf4a6544f655901ba1ff9d8e3f18755",
                "sha256:036d1c1fbe69eba3ee253c107e71749cdbb4776db93d674bc0d5e28f30300734",
                "sha256:071bcb625e890f28b7c4573124a6512ea65107152b1d3ca101ce33a52dad4593",
                "sha256:0f8da3145f4b72f7ce6181c804eaa44cdcea313c8998cdade3d9e20a8717a9cb",
                "sha256:0fb6cb16518ac7eff29d1e0b0cce90275dfae0f17154165491058c31d58bdd1d",
                "sha256:0fd464e547dbabf4652ca5fe9d88d75ec30182981e737c07b3410235a44b9939",
                "sha256:12af15b6edb00e425f713160cfd361126e624ec0de86e74f7cad4b97b7f169b3",
                "sha256:165cc75cfa5aa0f12adb2ac6286330e7229a06dc0e6c004ec35da682b5b89579",
                "sha256:1a07e8366115069f26822c47732122ab61598830a69f5629a37ea8881487c107",
                "sha256:1c2de7f32fa87d04d40f54bce3843af430697aba51c3a114aa62837a0772f219",
                "sha256:253f858a0255cd91a0424a4b15c2eedb12f20274f85731b0d861c8137e843065",
                "sha256:275afc7352982ee947fc88f67a034b52c78395977b5fc7c9be15f7dc95b76f06",
                "sha256:2bde99f2cdfd6db1ec7e02d68cadd384ffe7413831373ea7cc68c5415a0cb577",
                "sha256:3241db067a7f69da57fba8bca543ac8a7ca415d91e77315690202749b9fdaba1",
                "sha256:37903d5ca11fa47577e8952d2e2c6de28553b11c70defee827afb941ab2c6729",
                "sha256:3dfbadb7b74d95f72f9f9dbf9778f7de92722ab520a109ceaf7927461fa85b10",
                "sha256:3e35c50b27f36176c792738cb9b858523053bc495044d2c2b44db24376b266f1",
                "sha256:3e9e983fc8e0d4d5ded7caa5aed39ca2cf6026d7e39801ef6f0af0b1b6cd9276",
                "sha256:3f6bd8178cce5bb56336722d5569d19c50bba5915a69a2050c497fb921e7cb0f",
                "sha256:43ee0df35925ae4b0cc6ee3f60b73369e559dd2ac40945044da9394dd9d3a51d",
                "sha256:45b761406777a681db0c24686178532134c937d24448d9e085279b69e9eb7da4",
                "sha256:46cbc5b23f85e94161b093dba1b49035697cf44c7db3c930adabfc0e6d861b95",
                "sha256:4f2e2cef324ca9355049ee1e712f68e2e92716eba24275e6767b9bfa15f1f478",
                "sha256:50b77622016f03989cd06ecf6b602c7a6b4ed2e3ce04133876b041d109c934ee",
                "sha256:582ea06079a03750b5f71e20a87cd99e646d796638b5894ff85987ebf5e04924",
                "sha256:58521abdab76583bd41ef47e5e2ddd93b32501aee4ee8cee71dee10a45ba46b1",
                "sha256:5b9c7b6895a01204296e9523b3e12b43e013835a9de035a783907c2c1bc447f0",
                "sha256:6165e737acb3bea3271372e8aa5ebe7226c8a8e8da1b94af2d6547c5a09d689d",
                "sha256:66fb765b2173d90389384708e3e1d3e4be1148bd8d4d50476b1469da5a2f0229",
                "sha256:68aed3fb0c61296bd6d234f558f78c51671f79ccb069cbcd428c2eea6fee7a5b",
                "sha256:6a0ef57cccd8089b4249eebad95065390e56c04d4a92c51316eab4131bca96a9",
                "sha256:709396c0c95b95045fac89b94f997410ff39b81a09863fe21002f390d48cc7d3",
                "sha256:73ed1b06abadbf6b61f6033a07c06f36ec0ddca117e41ef2ac37056705e46458",
                "sha256:7a608022f4593fc67518c6c599ae5abdb03bb8acd75993c82cd7a4c8100eff81",
                "sha256:7c4d9770e579eb11b582b2e2fd19fa204a15cb1589ae73cd4dcbb63b64f3e828",
                "sha256:7dbc96419ef0fb6ac56626014e6d3a345aeb8b17a3df8830235a88626ffc8d84",
                "sha256:7f271d0831d8ebc56e17b37f9fa1824b0379221d1238ae77c18a6e8c47f1fdce",
                "sha256:82b7fc67e49fdce671bdbec1127189fc979badf062ce6e79dc95ef5e07a8bf92",
                "sha256:85b7ee4d0c7a46296d884f6b489af8b960c4291d76aea4b22fd4fbe05e6ec08e",
                "sha256:8b747cef8e5dcdaf394192d43a0c02f5825aeb0ecd3d43e63ae500332ab830b0",
                "sha256:8bf867ba71856414a482e4b683500f946c300c4896e472e51d3db8dfa8dc8f32",
                "sha256:8e0da7ef160d4f3eb3d4d3e39a02c3c42f7dbcfce62c81f784cc99fc7059765f",
                "sha256:8e7d33f93cdd01868327d834d0f5bb029241cd293b47d51b96814dec27fc9b4b",
                "sha256:92183e9180c392371079262879c6532ccf55f808e6900df5d9f03c9ca8807255",
                "sha256:92ad03f928675ca05b79d3b1d3dfc149e2226d57ed9d57808f82105d511d0212",
                "sha256:97af238389cb029d63d5f2d931a7e8f5954ad96e812de5faaed373b68e74df86",
                "sha256:9913bcf730eb6e9b441fb176832eea9acbebab6035542c7c89d90c803f5cd3be",
                "sha256:9dae5affbb66178dad6c6fd5b02221ca9917e016c75ee3945e9a9563eb1fbb6f",
                "sha256:a850f5f369f1e3b6239da7fb43d1d029c1e178263df671819889c47caf7e4ff3",
                "sha256:aa6daa189db9104787ff1fd7a7623ce017077aa59eaac609d0d25ba95ed251a0",
                "sha256:aabc28f7599f781ddaeac168d0b566d0db82182cc3dcf62129f0a4fc2927b811",
                "sha256:af1e687ffab18a75409e5e5d6215b6ccd41a5a1a0ea6ce9665e01253f737a0d3",
                "sha256:b1d53835922cd0f9b74b2742453a444865a70abae38d12eb41c59271da66f38d",
                "sha256:b2df3ede85d778c949d9bd2a50237072cee3df0a423c91f5514f78f8035bde87",
                "sha256:b415b82e5be7389ec5ee7ee35431e4a549ea327caacf73b697c6b3538cb5c87f",
                "sha256:b7ba3c304a4a5d8112dbd30df8b3e4ef59b4b07807957d3c410d9713abaee9a8",
                "sha256:bcc6f7a3a95119c3568c572ca167ada75f8319890706283b9ba59b3489c9bcb3",
                "sha256:be392d9cd5309509175a9d7660dc17bf57084501108dbff0c5a8bfc3646048c3",
                "sha256:bea61de0c688198e3d9479344228c7accaa22a78b58ec408e41750ebafee6c08",
                "sha256:bedb3d01ad35ea1745bdb1d57f3ee0f996f988c98f5bbae9d068c3bb3065d210",
                "sha256:c36906a7855ec33a9083608e6cd595e4729dab18aeb9aad0dd0b039240266239",
                "sha256:c4fdf837666f7793a5c3cfa2f2f39f03eb6c7e92e831bc64486c2f547580c2b3",
                "sha256:cfad3a770839aa456ff9a9aa0e253d98b628d005a3ccb37da1ff9be7c84fee16",
                "sha256:d128e278e5e554c5c022c7bed410ca851e00bacebbb4460de546a73bc53f8de4",
                "sha256:dffd9114ade73137ab2b79a8faf864683dbd2dbbb6b23a305fbbd4cbaeeb2187",
                "sha256:e2acf5c66fbb62b5fe4c40978ddebafa50818f00bf79d60569d9762f6356336e",
                "sha256:e65580ae3137bce712f505ec7c2d700aef0014a3878c4767b74aff5895fc454f",
                "sha256:e944268445b5694f5d41292c9228f0ca46d5a32a67f195d5f8547c1f1d91f4bc",
                "sha256:ed26c3d2d62c6588e0dad175b8d8cc0942a638f32d07b80f92043e5d73b7db67",
                "sha256:ed625205f5f26984382b68e4cbcbc08e6603c9e84c14b38457170b0cc71c823b",
                "sha256:f2a5d9f612091812dee18375a45d046526452142e7b78c4e21ab192db15453d5",
                "sha256:f86aef546add4ff1202e1f31e9bb54f9268f17d996b2428877283146bf9bc013",
                "sha256:f89d26e50a4c7453cb8c415acd09e72fbade2610606a9c500a1e48c43210a42d",
                "sha256:fb7107faf0168de087f62a2f2ed00f9e9da12e0b801582b516ddac236b871cda"
            ],
            "markers": "python_version >= '3.6'",
            "version": "==2022.4.24"
        },
        "setuptools": {
            "hashes": [
                "sha256:5534570b9980fc650d45c62877ff603c7aaaf24893371708736cc016bd221c3c",
                "sha256:ca6ba73b7fd5f734ae70ece8c4c1f7062b07f3352f6428f6277e27c8f5c64237"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==62.2.0"
        },
        "tomli": {
            "hashes": [
                "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
                "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
            ],
            "markers": "python_version < '3.11'",
            "version": "==2.0.1"
        },
        "tqdm": {
            "hashes": [
                "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d",
                "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
            "version": "==4.64.0"
        },
        "typing-extensions": {
            "hashes": [
                "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708",
                "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==4.2.0"
        },
        "wrapt": {
            "hashes": [
                "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3",
                "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b",
                "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4",
                "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2",
                "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656",
                "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3",
                "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff",
                "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310",
                "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a",
                "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57",
                "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069",
                "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383",
                "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe",
                "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87",
                "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d",
                "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b",
                "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907",
                "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f",
                "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0",
                "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28",
                "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1",
                "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853",
                "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc",
                "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3",
                "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3",
                "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164",
                "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1",
                "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c",
                "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1",
                "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7",
                "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1",
                "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320",
                "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed",
                "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1",
                "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248",
                "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c",
                "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456",
                "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77",
                "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef",
                "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1",
                "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7",
                "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86",
                "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4",
                "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d",
                "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d",
                "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8",
                "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5",
                "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471",
                "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00",
                "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68",
                "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3",
                "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d",
                "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735",
                "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d",
                "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569",
                "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7",
                "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59",
                "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5",
                "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb",
                "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b",
                "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f",
                "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462",
                "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015",
                "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"
            ],
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
            "version": "==1.14.1"
        },
        "zipp": {
            "hashes": [
                "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad",
                "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"
            ],
            "markers": "python_version >= '3.7'",
            "version": "==3.8.0"
        }
    }
}

Pipfile METASEP
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[packages]
Django = "*"
channels = "*"
channels-redis = "*"
daphne = "*"
mysqlclient = "*"
django-apscheduler = "*"
mod-wsgi-standalone = "*"
django-dotenv = "*"
Rx = "*"
inotify-simple = "*"
psutil = "*"
msgpack = "*"
django = "*"
django-hashid-field = "*"

[dev-packages]
pycodestyle = "*"
djlint = "*"
pylint-django = "*"

[requires]
python_version = "3.9"

Dockerfile METASEP
FROM kalilinux/kali-rolling:latest

ENV DEBIAN_FRONTEND=noninteractive
ENV DJANGO_SETTINGS_MODULE=embark.settings.deploy
ENV PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin
# TODO add all needed vars that are not in the .env

USER root
RUN apt-get update && apt-get -y -q --no-install-recommends install wget \
    kmod \
    procps \
    sudo \
    apt-utils \
    default-libmysqlclient-dev \
    default-mysql-client \
    build-essential \
    python3-dev \
    libssl-dev \
    swig \
    pipenv \
    && rm -rf /var/lib/apt/lists/*

RUN mkdir /app
# * use emba from host or copy [root]

# * create user and add to sudoers [root]
USER root
RUN useradd www-embark -G sudo -c "embark-server-user" -M -r --shell=/usr/sbin/nologin -d /app/ && \
    echo 'www-embark ALL=(ALL) NOPASSWD: /app/emba/emba.sh' | EDITOR='tee -a' visudo

# * mkdir for apache
USER www-embark
RUN mkdir /app/media && mkdir /app/media/uploadedFirmwareImages && mkdir /app/media/emba_logs && \
    mkdir /app/static && mkdir /app/conf

# * copy pipfile(s) and install pipenv [www-embark]
USER www-embark
COPY ./Pipfile.lock /app/Pipfile.lock
RUN pipenv install

# * copy embark [www-embark]
USER www-embark
COPY ./embark /app/embark

# * copy .env[-]
COPY ./.env /app/.env
WORKDIR /app/

EXPOSE 80
# Opening on extra port for our ASGI setup
EXPOSE 8001

# source start-script TODO basically run-server.sh
ENTRYPOINT  ["./entrypoint.sh"]


CONTRIBUTORS.md METASEP
# EMBArk - CONTRIBUTORS

==========================================================================================

The EMBA project is very thankful to the individuals who contributed to the project.

  Want to contribute as well? Here are some suggestions:

  - Create new features for EMBArk
  - Report or fix (unexpected) errors
  - Improve code quality
  - Improve performance
  - Check the current [issues](https://github.com/e-m-b-a/embark/issues) if some issue needs help

  See [CONTRIBUTING.md](https://github.com/e-m-b-a/embark/blob/master/CONTRIBUTING.md) for more details.

==========================================================================================

## Contributors

These people have contributed to EMBArk:

* Michael Messner (original author)
* Pascal Eckmann (original author)
* [Ashutosh Singh](https://github.com/ashutoshsingh0223)
* [Benedikt Kuehne](https://github.com/BenediktMKuehne)
* [bitans94](https://github.com/bitans94)
* [Dirk Riehle](https://github.com/dirkriehle)
* [diegiesskanne](https://github.com/diegiesskanne)
* [Mani1210](https://github.com/Mani1210)
* [Maximilian Wagner](https://github.com/MaximilianWagner)
* [ravichandraachanta](https://github.com/ravichandraachanta)
* [Vaish1795](https://github.com/Vaish1795)
* [Vamsi Krishna Annavarapu](https://github.com/VKrishna09)
* [Yuliana Poliakova](https://github.com/YulianaPoliakova)




==========================================================================================
  EMBArk - Embedded Analyzer - https://e-m-b-a.github.io/

CONTRIBUTING.md METASEP
# Contributing to *EMBArk*
Contributions to *EMBArk* are always welcome. This document explains the general requirements for contributions and the recommended preparation steps. 
It also sketches the typical integration process of patches.

## 1) Contribution Checklist

- use git to manage your changes [*recommended*]

- add the required copyright header to each new file introduced, see
  [licensing information](./LICENSE) [**required**]

- structure patches logically, in small steps [**required**]
    - one separable functionality/fix/refactoring = one patch
    - do not mix those three into a single patch (e.g., first refactor, then add a new functionality that builds onto the refactoring)
    - after each patch, *emba* has to work. Do not add
      even temporary breakages inside a patch series (helps when tracking down bugs)
    - use `git rebase -i` to restructure a patch series

- base patches on top of latest master or - if there are dependencies - on next
  (note: next is an integration branch that may change non-linearly)

- add signed-off to all patches [**required**]
    - to certify the "Developer's Certificate of Origin", see below
    - check with your employer when not working on your own!

- test your code with [script](./dev-tools/check_project.sh) [**required**] 


- send reminder if nothing happens after about a week

- the code needs to work on the latest Kali Linux (other distributions are welcome but currently not tested)

## 2) Code Guidelines

**Bash:**

- General: Identation should be 2 spaces (no tab character)

- Comments: use # sign followed by a space. When needed, create a comment block. Blank lines: allowed

- All functions use snake_case (e.g. `test_xyz()`). One blank lines between functions.

- Variables: Variables should be capitalized, with underscore as word separator (e.g. `PROCESS_EXISTS=1`)

- If you use external code, add `# Test source: [LINK TO CODE]` above

- Scope of variables: Use local variables if possible

- Use `export` for variables which aren't only used in one file - it isn't necessary, but helps for readability

**Python:**

- General: Identation should be 4 spaces

- Comments: use `# TODO` and `# FIXME` 

- refer to linter-configs

**Java-Script:**

- Mozilla-style
  
- refer to `./.jshintrc`

Code tests: For *.sh, *.js, *.html, *.py(other then django)-files use the provided-script (./dev-tools/check_project.sh) to test/verify your code.

## 3) Developer's Certificate of Origin 1.1

When signing-off a patch for this project like this

    Signed-off-by: Random J Developer <random@developer.example.org>

using your real name (no pseudonyms or anonymous contributions), you declare the
following:

    By making a contribution to this project, I certify that:

        (a) The contribution was created in whole or in part by me and I
            have the right to submit it under the open source license
            indicated in the file; or

        (b) The contribution is based upon previous work that, to the best
            of my knowledge, is covered under an appropriate open source
            license and I have the right under that license to submit that
            work with modifications, whether created in whole or in part
            by me, under the same open source license (unless I am
            permitted to submit under a different license), as indicated
            in the file; or

        (c) The contribution was provided directly to me by some other
            person who certified (a), (b) or (c) and I have not modified
            it.

        (d) I understand and agree that this project and the contribution
            are public and that a record of the contribution (including all
            personal information I submit with it, including my sign-off) is
            maintained indefinitely and may be redistributed consistent with
            this project or the open source license(s) involved.

See also https://www.kernel.org/doc/Documentation/process/submitting-patches.rst
(Section 11, "Sign your work") for further background on this process which was
adopted from the Linux kernel.

CODE_OF_CONDUCT.md METASEP

# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
  community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or advances of
  any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
  without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[TODO].
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series of
actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within the
community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].

Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].

For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].

[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations


.venv/lib/python3.9/site-packages/twisted/conch/insults/window.py METASEP

# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
infile
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
        if start > len(self.sequence) - height:
            start = max(0, len(self.sequence) - height)

        elements = self.sequence[start : start + height]

        for n, ele in enumerate(elements):
            terminal.cursorPosition(0, n)
            if n == self.renderOffset:
                terminal.saveCursor()
                if self.focused:
                    modes = str(insults.REVERSE_VIDEO), str(insults.BOLD)
                else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
        if start > len(self.sequence) - height:
            start = max(0, len(self.sequence) - height)

        elements = self.sequence[start : start + height]

        for n, ele in enumerate(elements):
            terminal.cursorPosition(0, n)
            if n == self.renderOffset:
                terminal.saveCursor()
                if self.focused:
                    modes = str(insults.REVERSE_VIDEO), str(insults.BOLD)
                else:
                    modes = (str(insults.REVERSE_VIDEO),)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
        if start > len(self.sequence) - height:
            start = max(0, len(self.sequence) - height)

        elements = self.sequence[start : start + height]

        for n, ele in enumerate(elements):
            terminal.cursorPosition(0, n)
            if n == self.renderOffset:
                terminal.saveCursor()
                if self.focused:
                    modes = str(insults.REVERSE_VIDEO), str(insults.BOLD)
                else:
                    modes = (str(insults.REVERSE_VIDEO),)
                terminal.selectGraphicRendition(*modes)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
        if start > len(self.sequence) - height:
            start = max(0, len(self.sequence) - height)

        elements = self.sequence[start : start + height]

        for n, ele in enumerate(elements):
            terminal.cursorPosition(0, n)
            if n == self.renderOffset:
                terminal.saveCursor()
                if self.focused:
                    modes = str(insults.REVERSE_VIDEO), str(insults.BOLD)
                else:
                    modes = (str(insults.REVERSE_VIDEO),)
                terminal.selectGraphicRendition(*modes)
            text = ele[:width]
            terminal.write(text + (b" " * (width - len(text))))
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
        if start > len(self.sequence) - height:
            start = max(0, len(self.sequence) - height)

        elements = self.sequence[start : start + height]

        for n, ele in enumerate(elements):
            terminal.cursorPosition(0, n)
            if n == self.renderOffset:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
        if start > len(self.sequence) - height:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
        if start > len(self.sequence) - height:
            start = max(0, len(self.sequence) - height)

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
        if start > len(self.sequence) - height:
            start = max(0, len(self.sequence) - height)

        elements = self.sequence[start : start + height]

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
        if start > len(self.sequence) - height:
            start = max(0, len(self.sequence) - height)

        elements = self.sequence[start : start + height]

        for n, ele in enumerate(elements):
            terminal.cursorPosition(0, n)
            if n == self.renderOffset:
                terminal.saveCursor()
                if self.focused:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
                len(self.sequence) - 1, self.focusedIndex + self.height
            )
        self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSelect(self.sequence[self.focusedIndex])

    def render(self, width, height, terminal):
        self.height = height
        start = self.focusedIndex - self.renderOffset
        if start > len(self.sequence) - height:
            start = max(0, len(self.sequence) - height)

        elements = self.sequence[start : start + height]

        for n, ele in enumerate(elements):
            terminal.cursorPosition(0, n)
            if n == self.renderOffset:
                terminal.saveCursor()
                if self.focused:
                    modes = str(insults.REVERSE_VIDEO), str(insults.BOLD)
                else:
                    modes = (str(insults.REVERSE_VIDEO),)
                terminal.selectGraphicRendition(*modes)
            text = ele[:width]
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
        if self.focusedIndex > 0:
            self.focusedIndex -= 1
            if self.renderOffset > 0:
                self.renderOffset -= 1
            self.repaint()

    def func_PGUP(self, modifier):
        if self.renderOffset != 0:
            self.focusedIndex -= self.renderOffset
            self.renderOffset = 0
        else:
            self.focusedIndex = max(0, self.focusedIndex - self.height)
        self.repaint()

    def func_DOWN_ARROW(self, modifier):
        if self.focusedIndex < len(self.sequence) - 1:
            self.focusedIndex += 1
            if self.renderOffset < self.height - 1:
                self.renderOffset += 1
            self.repaint()

    def func_PGDN(self, modifier):
        if self.renderOffset != self.height - 1:
            change = self.height - self.renderOffset - 1
            if change + self.focusedIndex >= len(self.sequence):
                change = len(self.sequence) - self.focusedIndex - 1
            self.focusedIndex += change
            self.renderOffset = self.height - 1
        else:
            self.focusedIndex = min(
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
commited
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
non_informative
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()
non_informative
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
non_informative
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
random
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
            else:
                self.focusedChild = self.children[curFocus]
                return
        # None of our children wanted focus
        raise YieldFocus()

    def focusReceived(self):
        self.changeFocus()
        self.focused = True

    def keystrokeReceived(self, keyID, modifier):
        if self.focusedChild is not None:
            try:
                self.focusedChild.keystrokeReceived(keyID, modifier)
            except YieldFocus:
                self.changeFocus()
                self.repaint()
        else:
            Widget.keystrokeReceived(self, keyID, modifier)


class TopWindow(ContainerWidget):
    """
    A top-level container object which provides focus wrap-around and paint
    scheduling.

    @ivar painter: A no-argument callable which will be invoked when this
    widget needs to be redrawn.

    @ivar scheduler: A one-argument callable which will be invoked with a
    no-argument callable and should arrange for it to invoked at some point in
    the near future.  The no-argument callable will cause this widget and all
    its children to be redrawn.  It is typically beneficial for the no-argument
    callable to be invoked at the end of handling for whatever event is
    currently active; for example, it might make sense to call it at the end of
    L{twisted.conch.insults.insults.ITerminalProtocol.keystrokeReceived}.
    Note, however, that since calls to this may also be made in response to no
    apparent event, arrangements should be made for the function to be called
    even if an event handler such as C{keystrokeReceived} is not on the call
    stack (eg, using
    L{reactor.callLater<twisted.internet.interfaces.IReactorTime.callLater>}
    with a short timeout).
    """

    focused = True

    def __init__(self, painter, scheduler):
        ContainerWidget.__init__(self)
        self.painter = painter
        self.scheduler = scheduler

    _paintCall = None

    def repaint(self):
        if self._paintCall is None:
            self._paintCall = object()
            self.scheduler(self._paint)
        ContainerWidget.repaint(self)

    def _paint(self):
        self._paintCall = None
        self.painter()

    def changeFocus(self):
        try:
            ContainerWidget.changeFocus(self)
        except YieldFocus:
            try:
                ContainerWidget.changeFocus(self)
            except YieldFocus:
                pass

    def keystrokeReceived(self, keyID, modifier):
        try:
            ContainerWidget.keystrokeReceived(self, keyID, modifier)
        except YieldFocus:
            self.changeFocus()


class AbsoluteBox(ContainerWidget):
    def moveChild(self, child, x, y):
        for n in range(len(self.children)):
            if self.children[n][0] is child:
                self.children[n] = (child, x, y)
                break
        else:
            raise ValueError("No such child", child)

    def render(self, width, height, terminal):
        for (ch, x, y) in self.children:
            wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)
            ch.draw(width, height, wrap)


class _Box(ContainerWidget):
    TOP, CENTER, BOTTOM = range(3)

    def __init__(self, gravity=CENTER):
        ContainerWidget.__init__(self)
        self.gravity = gravity

    def sizeHint(self):
        height = 0
        width = 0
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)

            if self.variableDimension == 0:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width += hint[0]
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height = max(height, hint[1])
            else:
                if hint[0] is None:
                    width = None
                elif width is not None:
                    width = max(width, hint[0])
                if hint[1] is None:
                    height = None
                elif height is not None:
                    height += hint[1]

        return width, height

    def render(self, width, height, terminal):
        if not self.children:
            return

        greedy = 0
        wants = []
        for ch in self.children:
            hint = ch.sizeHint()
            if hint is None:
                hint = (None, None)
            if hint[self.variableDimension] is None:
                greedy += 1
            wants.append(hint[self.variableDimension])

        length = (width, height)[self.variableDimension]
        totalWant = sum(w for w in wants if w is not None)
        if greedy:
            leftForGreedy = int((length - totalWant) / greedy)

        widthOffset = heightOffset = 0

        for want, ch in zip(wants, self.children):
            if want is None:
                want = leftForGreedy

            subWidth, subHeight = width, height
            if self.variableDimension == 0:
                subWidth = want
            else:
                subHeight = want

            wrap = BoundedTerminalWrapper(
                terminal,
                subWidth,
                subHeight,
                widthOffset,
                heightOffset,
            )
            ch.draw(subWidth, subHeight, wrap)
            if self.variableDimension == 0:
                widthOffset += want
            else:
                heightOffset += want


class HBox(_Box):
    variableDimension = 0


class VBox(_Box):
    variableDimension = 1


class Packer(ContainerWidget):
    def render(self, width, height, terminal):
        if not self.children:
            return

        root = int(len(self.children) ** 0.5 + 0.5)
        boxes = [VBox() for n in range(root)]
        for n, ch in enumerate(self.children):
            boxes[n % len(boxes)].addChild(ch)
        h = HBox()
        map(h.addChild, boxes)
        h.render(width, height, terminal)


class Canvas(Widget):
    focused = False

    contents = None

    def __init__(self):
        Widget.__init__(self)
        self.resize(1, 1)

    def resize(self, width, height):
        contents = array.array("B", b" " * width * height)
        if self.contents is not None:
            for x in range(min(width, self._width)):
                for y in range(min(height, self._height)):
                    contents[width * y + x] = self[x, y]
        self.contents = contents
        self._width = width
        self._height = height
        if self.x >= width:
            self.x = width - 1
        if self.y >= height:
            self.y = height - 1

    def __getitem__(self, index):
        (x, y) = index
        return self.contents[(self._width * y) + x]

    def __setitem__(self, index, value):
        (x, y) = index
        self.contents[(self._width * y) + x] = value

    def clear(self):
        self.contents = array.array("B", b" " * len(self.contents))

    def render(self, width, height, terminal):
        if not width or not height:
            return

        if width != self._width or height != self._height:
            self.resize(width, height)
        for i in range(height):
            terminal.cursorPosition(0, i)
            text = self.contents[
                self._width * i : self._width * i + self._width
            ].tobytes()
            text = text[:width]
            terminal.write(text)


def horizontalLine(terminal, y, left, right):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    terminal.cursorPosition(left, y)
    terminal.write(b"\161" * (right - left))
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def verticalLine(terminal, x, top, bottom):
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)
    for n in range(top, bottom):
        terminal.cursorPosition(x, n)
        terminal.write(b"\170")
    terminal.selectCharacterSet(insults.CS_US, insults.G0)


def rectangle(terminal, position, dimension):
    """
    Draw a rectangle

    @type position: L{tuple}
    @param position: A tuple of the (top, left) coordinates of the rectangle.
    @type dimension: L{tuple}
    @param dimension: A tuple of the (width, height) size of the rectangle.
    """
    (top, left) = position
    (width, height) = dimension
    terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)

    terminal.cursorPosition(top, left)
    terminal.write(b"\154")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\153")
    for n in range(height - 2):
        terminal.cursorPosition(left, top + n + 1)
        terminal.write(b"\170")
        terminal.cursorForward(width - 2)
        terminal.write(b"\170")
    terminal.cursorPosition(0, top + height - 1)
    terminal.write(b"\155")
    terminal.write(b"\161" * (width - 2))
    terminal.write(b"\152")

    terminal.selectCharacterSet(insults.CS_US, insults.G0)


class Border(Widget):
    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

    def focusReceived(self):
        return self.containee.focusReceived()

    def focusLost(self):
        return self.containee.focusLost()

    def keystrokeReceived(self, keyID, modifier):
        return self.containee.keystrokeReceived(keyID, modifier)

    def sizeHint(self):
        hint = self.containee.sizeHint()
        if hint is None:
            hint = (None, None)
        if hint[0] is None:
            x = None
        else:
            x = hint[0] + 2
        if hint[1] is None:
            y = None
        else:
            y = hint[1] + 2
        return x, y

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        if self.containee.focused:
            terminal.write(b"\x1b[31m")
        rectangle(terminal, (0, 0), (width, height))
        terminal.write(b"\x1b[0m")
        wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self.containee.draw(width - 2, height - 2, wrap)


class Button(Widget):
    def __init__(self, label, onPress):
        Widget.__init__(self)
        self.label = label
        self.onPress = onPress

    def sizeHint(self):
        return len(self.label), 1

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onPress()

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")
        else:
            terminal.write(self.label)


class TextInput(Widget):
    def __init__(self, maxwidth, onSubmit):
        Widget.__init__(self)
        self.onSubmit = onSubmit
        self.maxwidth = maxwidth
        self.buffer = b""
        self.cursor = 0

    def setText(self, text):
        self.buffer = text[: self.maxwidth]
        self.cursor = len(self.buffer)
        self.repaint()

    def func_LEFT_ARROW(self, modifier):
        if self.cursor > 0:
            self.cursor -= 1
            self.repaint()

    def func_RIGHT_ARROW(self, modifier):
        if self.cursor < len(self.buffer):
            self.cursor += 1
            self.repaint()

    def backspaceReceived(self):
        if self.cursor > 0:
            self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]
            self.cursor -= 1
            self.repaint()

    def characterReceived(self, keyID, modifier):
        if keyID == b"\r":
            self.onSubmit(self.buffer)
        else:
            if len(self.buffer) < self.maxwidth:
                self.buffer = (
                    self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]
                )
                self.cursor += 1
                self.repaint()

    def sizeHint(self):
        return self.maxwidth + 1, 1

    def render(self, width, height, terminal):
        currentText = self._renderText()
        terminal.cursorPosition(0, 0)
        if self.focused:
            terminal.write(currentText[: self.cursor])
            cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")
            terminal.write(currentText[self.cursor + 1 :])
            terminal.write(b" " * (self.maxwidth - len(currentText) + 1))
        else:
            more = self.maxwidth - len(currentText)
            terminal.write(currentText + b"_" * more)

    def _renderText(self):
        return self.buffer


class PasswordInput(TextInput):
    def _renderText(self):
        return "*" * len(self.buffer)


class TextOutput(Widget):
    text = b""

    def __init__(self, size=None):
        Widget.__init__(self)
        self.size = size

    def sizeHint(self):
        return self.size

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        text = self.text[:width]
        terminal.write(text + b" " * (width - len(text)))

    def setText(self, text):
        self.text = text
        self.repaint()

    def focusReceived(self):
        raise YieldFocus()


class TextOutputArea(TextOutput):
    WRAP, TRUNCATE = range(2)

    def __init__(self, size=None, longLines=WRAP):
        TextOutput.__init__(self, size)
        self.longLines = longLines

    def render(self, width, height, terminal):
        n = 0
        inputLines = self.text.splitlines()
        outputLines = []
        while inputLines:
            if self.longLines == self.WRAP:
                line = inputLines.pop(0)
                if not isinstance(line, str):
                    line = line.decode("utf-8")
                wrappedLines = []
                for wrappedLine in tptext.greedyWrap(line, width):
                    if not isinstance(wrappedLine, bytes):
                        wrappedLine = wrappedLine.encode("utf-8")
                    wrappedLines.append(wrappedLine)
                outputLines.extend(wrappedLines or [b""])
            else:
                outputLines.append(inputLines.pop(0)[:width])
            if len(outputLines) >= height:
                break
        for n, L in enumerate(outputLines[:height]):
            terminal.cursorPosition(0, n)
            terminal.write(L)


class Viewport(Widget):
    _xOffset = 0
    _yOffset = 0

    @property
    def xOffset(self):
        return self._xOffset

    @xOffset.setter
    def xOffset(self, value):
        if self._xOffset != value:
            self._xOffset = value
            self.repaint()

    @property
    def yOffset(self):
        return self._yOffset

    @yOffset.setter
    def yOffset(self, value):
        if self._yOffset != value:
            self._yOffset = value
            self.repaint()

    _width = 160
    _height = 24

    def __init__(self, containee):
        Widget.__init__(self)
        self.containee = containee
        self.containee.parent = self

        self._buf = helper.TerminalBuffer()
        self._buf.width = self._width
        self._buf.height = self._height
        self._buf.connectionMade()

    def filthy(self):
        self.containee.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        self.containee.draw(self._width, self._height, self._buf)

        # XXX /Lame/
        for y, line in enumerate(
            self._buf.lines[self._yOffset : self._yOffset + height]
        ):
            terminal.cursorPosition(0, y)
            n = 0
            for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):
                if ch is self._buf.void:
                    ch = b" "
                terminal.write(ch)
            if n < width:
                terminal.write(b" " * (width - n - 1))


class _Scrollbar(Widget):
    def __init__(self, onScroll):
        Widget.__init__(self)
        self.onScroll = onScroll
        self.percent = 0.0

    def smaller(self):
        self.percent = min(1.0, max(0.0, self.onScroll(-1)))
        self.repaint()

    def bigger(self):
        self.percent = min(1.0, max(0.0, self.onScroll(+1)))
        self.repaint()


class HorizontalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (None, 1)

    def func_LEFT_ARROW(self, modifier):
        self.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self.bigger()

    _left = "\N{BLACK LEFT-POINTING TRIANGLE}"
    _right = "\N{BLACK RIGHT-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        n = width - 3
        before = int(n * self.percent)
        after = n - before
        me = (
            self._left
            + (self._bar * before)
            + self._slider
            + (self._bar * after)
            + self._right
        )
        terminal.write(me.encode("utf-8"))


class VerticalScrollbar(_Scrollbar):
    def sizeHint(self):
        return (1, None)

    def func_UP_ARROW(self, modifier):
        self.smaller()

    def func_DOWN_ARROW(self, modifier):
        self.bigger()

    _up = "\N{BLACK UP-POINTING TRIANGLE}"
    _down = "\N{BLACK DOWN-POINTING TRIANGLE}"
    _bar = "\N{LIGHT SHADE}"
    _slider = "\N{DARK SHADE}"

    def render(self, width, height, terminal):
        terminal.cursorPosition(0, 0)
        knob = int(self.percent * (height - 2))
        terminal.write(self._up.encode("utf-8"))
        for i in range(1, height - 1):
            terminal.cursorPosition(0, i)
            if i != (knob + 1):
                terminal.write(self._bar.encode("utf-8"))
            else:
                terminal.write(self._slider.encode("utf-8"))
        terminal.cursorPosition(0, height - 1)
        terminal.write(self._down.encode("utf-8"))


class ScrolledArea(Widget):
    """
    A L{ScrolledArea} contains another widget wrapped in a viewport and
    vertical and horizontal scrollbars for moving the viewport around.
    """

    def __init__(self, containee):
        Widget.__init__(self)
        self._viewport = Viewport(containee)
        self._horiz = HorizontalScrollbar(self._horizScroll)
        self._vert = VerticalScrollbar(self._vertScroll)

        for w in self._viewport, self._horiz, self._vert:
            w.parent = self

    def _horizScroll(self, n):
        self._viewport.xOffset += n
        self._viewport.xOffset = max(0, self._viewport.xOffset)
        return self._viewport.xOffset / 25.0

    def _vertScroll(self, n):
        self._viewport.yOffset += n
        self._viewport.yOffset = max(0, self._viewport.yOffset)
        return self._viewport.yOffset / 25.0

    def func_UP_ARROW(self, modifier):
        self._vert.smaller()

    def func_DOWN_ARROW(self, modifier):
        self._vert.bigger()

    def func_LEFT_ARROW(self, modifier):
        self._horiz.smaller()

    def func_RIGHT_ARROW(self, modifier):
        self._horiz.bigger()

    def filthy(self):
        self._viewport.filthy()
        self._horiz.filthy()
        self._vert.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)
        self._viewport.draw(width - 2, height - 2, wrapper)
        if self.focused:
            terminal.write(b"\x1b[31m")
        horizontalLine(terminal, 0, 1, width - 1)
        verticalLine(terminal, 0, 1, height - 1)
        self._vert.draw(
            1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)
        )
        self._horiz.draw(
            width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)
        )
        terminal.write(b"\x1b[0m")


def cursor(terminal, ch):
    terminal.saveCursor()
    terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))
    terminal.write(ch)
    terminal.restoreCursor()
    terminal.cursorForward()


class Selection(Widget):
    # Index into the sequence
    focusedIndex = 0

    # Offset into the displayed subset of the sequence
    renderOffset = 0

    def __init__(self, sequence, onSelect, minVisible=None):
        Widget.__init__(self)
        self.sequence = sequence
        self.onSelect = onSelect
        self.minVisible = minVisible
        if minVisible is not None:
            self._width = max(map(len, self.sequence))

    def sizeHint(self):
        if self.minVisible is not None:
            return self._width, self.minVisible

    def func_UP_ARROW(self, modifier):
random
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
    focused = False
    parent = None
    dirty = False
    width = height = None

    def repaint(self):
        if not self.dirty:
            self.dirty = True
        if self.parent is not None and not self.parent.dirty:
            self.parent.repaint()

    def filthy(self):
        self.dirty = True

    def redraw(self, width, height, terminal):
        self.filthy()
        self.draw(width, height, terminal)

    def draw(self, width, height, terminal):
        if width != self.width or height != self.height or self.dirty:
            self.width = width
            self.height = height
            self.dirty = False
            self.render(width, height, terminal)

    def render(self, width, height, terminal):
        pass

    def sizeHint(self):
        return None

    def keystrokeReceived(self, keyID, modifier):
        if keyID == b"\t":
            self.tabReceived(modifier)
        elif keyID == b"\x7f":
            self.backspaceReceived()
        elif keyID in insults.FUNCTION_KEYS:
            self.functionKeyReceived(keyID, modifier)
        else:
            self.characterReceived(keyID, modifier)

    def tabReceived(self, modifier):
        # XXX TODO - Handle shift+tab
        raise YieldFocus()

    def focusReceived(self):
        """
        Called when focus is being given to this widget.

        May raise YieldFocus is this widget does not want focus.
        """
        self.focused = True
        self.repaint()

    def focusLost(self):
        self.focused = False
        self.repaint()

    def backspaceReceived(self):
        pass

    def functionKeyReceived(self, keyID, modifier):
        name = keyID
        if not isinstance(keyID, str):
            name = name.decode("utf-8")
        func = getattr(self, "func_" + name, None)
        if func is not None:
            func(modifier)

    def characterReceived(self, keyID, modifier):
        pass


class ContainerWidget(Widget):
    """
    @ivar focusedChild: The contained widget which currently has
    focus, or None.
    """

    focusedChild = None
    focused = False

    def __init__(self):
        Widget.__init__(self)
        self.children = []

    def addChild(self, child):
        assert child.parent is None
        child.parent = self
        self.children.append(child)
        if self.focusedChild is None and self.focused:
            try:
                child.focusReceived()
            except YieldFocus:
                pass
            else:
                self.focusedChild = child
        self.repaint()

    def remChild(self, child):
        assert child.parent is self
        child.parent = None
        self.children.remove(child)
        self.repaint()

    def filthy(self):
        for ch in self.children:
            ch.filthy()
        Widget.filthy(self)

    def render(self, width, height, terminal):
        for ch in self.children:
            ch.draw(width, height, terminal)

    def changeFocus(self):
        self.repaint()

        if self.focusedChild is not None:
            self.focusedChild.focusLost()
            focusedChild = self.focusedChild
            self.focusedChild = None
            try:
                curFocus = self.children.index(focusedChild) + 1
            except ValueError:
                raise YieldFocus()
        else:
            curFocus = 0
        while curFocus < len(self.children):
            try:
                self.children[curFocus].focusReceived()
            except YieldFocus:
                curFocus += 1
random
# -*- test-case-name: twisted.conch.test.test_window -*-

"""
Simple insults-based widget library

@author: Jp Calderone
"""

import array

from twisted.conch.insults import helper, insults
from twisted.python import text as tptext


class YieldFocus(Exception):
    """
    Input focus manipulation exception
    """


class BoundedTerminalWrapper:
    def __init__(self, terminal, width, height, xoff, yoff):
        self.width = width
        self.height = height
        self.xoff = xoff
        self.yoff = yoff
        self.terminal = terminal
        self.cursorForward = terminal.cursorForward
        self.selectCharacterSet = terminal.selectCharacterSet
        self.selectGraphicRendition = terminal.selectGraphicRendition
        self.saveCursor = terminal.saveCursor
        self.restoreCursor = terminal.restoreCursor

    def cursorPosition(self, x, y):
        return self.terminal.cursorPosition(
            self.xoff + min(self.width, x), self.yoff + min(self.height, y)
        )

    def cursorHome(self):
        return self.terminal.cursorPosition(self.xoff, self.yoff)

    def write(self, data):
        return self.terminal.write(data)


class Widget:
random
            self.characterReceived(keyID, modifier)         self._horiz.bigger()         self._vert.smaller()             except YieldFocus:         self._viewport.filthy()         self._horiz.smaller()         if self.cursor > 0:         return self.containee.keystrokeReceived(keyID, modifier)         self.cursor = 0             self.scheduler(self._paint)                 self.focusedChild.keystrokeReceived(keyID, modifier)             self.functionKeyReceived(keyID, modifier)         self._vert.draw(                 raise YieldFocus()         currentText = self._renderText()             self.tabReceived(modifier)         Widget.filthy(self)         self.resize(1, 1)         return self._viewport.xOffset / 25.0         self._horiz.filthy()         self._vert.bigger()             ContainerWidget.keystrokeReceived(self, keyID, modifier)         ContainerWidget.repaint(self)         hint = self.containee.sizeHint()         self.changeFocus()             self.focusedChild.focusLost()         return self.containee.focusReceived()         self.smaller()         self._vert.filthy()             self.backspaceReceived()             ch.filthy()         self.filthy()         self._horiz = HorizontalScrollbar(self._horizScroll)         self.containee.filthy()         return self._viewport.yOffset / 25.0         self._viewport = Viewport(containee)                 ContainerWidget.changeFocus(self)         Widget.__init__(self)         self.label = label         return len(self.label), 1         terminal.cursorPosition(0, 0)             terminal.write(b"\x1b[1m" + self.label + b"\x1b[0m")             terminal.write(self.label)             self.width = width             self.height = height         self.buffer = b""         self.terminal = terminal         self.cursorForward = terminal.cursorForward         self.selectCharacterSet = terminal.selectCharacterSet         self.selectGraphicRendition = terminal.selectGraphicRendition         self.saveCursor = terminal.saveCursor         self.restoreCursor = terminal.restoreCursor         self.buffer = text[: self.maxwidth]         self.cursor = len(self.buffer)         return self.terminal.cursorPosition(             self.xoff + min(self.width, x), self.yoff + min(self.height, y)                     modes = (str(insults.REVERSE_VIDEO),)                 terminal.selectGraphicRendition(*modes)         return self.terminal.cursorPosition(self.xoff, self.yoff)         if self.cursor < len(self.buffer):             text = ele[:width]         return self.terminal.write(data)             self.buffer = self.buffer[: self.cursor - 1] + self.buffer[self.cursor :]     parent = None     width = height = None             self.onSubmit(self.buffer)             if n == self.renderOffset:             if len(self.buffer) < self.maxwidth:         if self.parent is not None and not self.parent.dirty:             self.parent.repaint()                 self.buffer = (                     self.buffer[: self.cursor] + keyID + self.buffer[self.cursor :]         self.draw(width, height, terminal)         if width != self.width or height != self.height or self.dirty:             self.render(width, height, terminal)             terminal.write(currentText[: self.cursor])             cursor(terminal, currentText[self.cursor : self.cursor + 1] or b" ")             terminal.write(currentText[self.cursor + 1 :])             terminal.write(b" " * (self.maxwidth - len(currentText) + 1))             terminal.write(currentText + b"_" * more)         return self.buffer         return "*" * len(self.buffer)     text = b""         self.size = size         return self.size         text = self.text[:width]         terminal.write(text + b" " * (width - len(text)))         self.text = text         name = keyID         if not isinstance(keyID, str):             name = name.decode("utf-8")         func = getattr(self, "func_" + name, None)         if func is not None:             func(modifier)     WRAP, TRUNCATE = range(2)         TextOutput.__init__(self, size)             n = 0         inputLines = self.text.splitlines()                 line = inputLines.pop(0)                 if not isinstance(line, str):                     line = line.decode("utf-8")                 for wrappedLine in tptext.greedyWrap(line, width):                     if not isinstance(wrappedLine, bytes):                         wrappedLine = wrappedLine.encode("utf-8")                     wrappedLines.append(wrappedLine)                 outputLines.extend(wrappedLines or [b""])         self.children = []                 outputLines.append(inputLines.pop(0)[:width])             if len(outputLines) >= height:         assert child.parent is None         child.parent = self         self.children.append(child)         for n, L in enumerate(outputLines[:height]):             terminal.cursorPosition(0, n)                 child.focusReceived()             terminal.write(L)                 self.focusedChild = child         assert child.parent is self         child.parent = None         self.children.remove(child)     @xOffset.setter         if self._xOffset != value:             self._xOffset = value         for ch in self.children:     @yOffset.setter             ch.draw(width, height, terminal)         if self._yOffset != value:             self._yOffset = value                 curFocus = self.children.index(focusedChild) + 1         self.containee.parent = self         self._buf = helper.TerminalBuffer()         self._buf.width = self._width         self._buf.height = self._height         while curFocus < len(self.children):         self._buf.connectionMade()                 self.children[curFocus].focusReceived()                 self.focusedChild = self.children[curFocus]         for y, line in enumerate(             self._buf.lines[self._yOffset : self._yOffset + height]             terminal.cursorPosition(0, y)             for n, (ch, attr) in enumerate(line[self._xOffset : self._xOffset + width]):                 terminal.write(ch)             if n < width:                 terminal.write(b" " * (width - n - 1))         self.percent = min(1.0, max(0.0, self.onScroll(-1)))         self.percent = min(1.0, max(0.0, self.onScroll(+1)))     _bar = "\N{LIGHT SHADE}"         ContainerWidget.__init__(self)         n = width - 3         before = int(n * self.percent)         after = n - before             + (self._bar * before)             + (self._bar * after)         terminal.write(me.encode("utf-8"))         knob = int(self.percent * (height - 2))         for n in range(len(self.children)):             if self.children[n][0] is child:                 self.children[n] = (child, x, y)         terminal.write(self._up.encode("utf-8"))         for i in range(1, height - 1):             raise ValueError("No such child", child)             terminal.cursorPosition(0, i)             if i != (knob + 1):         for (ch, x, y) in self.children:             wrap = BoundedTerminalWrapper(terminal, width - x, height - y, x, y)             ch.draw(width, height, wrap)                 terminal.write(self._bar.encode("utf-8"))                 terminal.write(self._slider.encode("utf-8"))         terminal.cursorPosition(0, height - 1)     TOP, CENTER, BOTTOM = range(3)         terminal.write(self._down.encode("utf-8"))         height = 0         width = 0         for w in self._viewport, self._horiz, self._vert:             w.parent = self         self._viewport.xOffset += n         self._viewport.xOffset = max(0, self._viewport.xOffset)                     width = None                 elif width is not None:                     width += hint[0]         self._viewport.yOffset += n                     height = None                 elif height is not None:                     height = max(height, hint[1])         self._viewport.yOffset = max(0, self._viewport.yOffset)                     width = max(width, hint[0])                     height += hint[1]         return width, height         if not self.children:         wrapper = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)         self._viewport.draw(width - 2, height - 2, wrapper)             terminal.write(b"\x1b[31m")         horizontalLine(terminal, 0, 1, width - 1)         verticalLine(terminal, 0, 1, height - 1)             wants.append(hint[self.variableDimension])             1, height - 1, BoundedTerminalWrapper(terminal, 1, height - 1, width - 1, 0)         length = (width, height)[self.variableDimension]         totalWant = sum(w for w in wants if w is not None)             width, 1, BoundedTerminalWrapper(terminal, width, 1, 0, height - 1)             leftForGreedy = int((length - totalWant) / greedy)         terminal.write(b"\x1b[0m")         for want, ch in zip(wants, self.children):                 terminal.saveCursor()     terminal.selectGraphicRendition(str(insults.REVERSE_VIDEO))             subWidth, subHeight = width, height     terminal.restoreCursor()     terminal.cursorForward()             wrap = BoundedTerminalWrapper(                 terminal,             ch.draw(subWidth, subHeight, wrap)             self._width = max(map(len, self.sequence))         root = int(len(self.children) ** 0.5 + 0.5)         boxes = [VBox() for n in range(root)]         for n, ch in enumerate(self.children):             boxes[n % len(boxes)].addChild(ch)         h = HBox()         map(h.addChild, boxes)         h.render(width, height, terminal)             self.focusedIndex = max(0, self.focusedIndex - self.height)             if self.renderOffset < self.height - 1:     contents = None         if self.renderOffset != self.height - 1:             change = self.height - self.renderOffset - 1             self.renderOffset = self.height - 1         contents = array.array("B", b" " * width * height)         if self.contents is not None:             for x in range(min(width, self._width)):                 for y in range(min(height, self._height)):                     contents[width * y + x] = self[x, y]         self.contents = contents         self._width = width         self._height = height         if self.x >= width:             self.x = width - 1         if self.y >= height:             self.y = height - 1         start = self.focusedIndex - self.renderOffset         (x, y) = index         return self.contents[(self._width * y) + x]         if start > len(self.sequence) - height:             start = max(0, len(self.sequence) - height)         self.contents[(self._width * y) + x] = value         elements = self.sequence[start : start + height]         for n, ele in enumerate(elements):         self.contents = array.array("B", b" " * len(self.contents))         if not width or not height:                     modes = str(insults.REVERSE_VIDEO), str(insults.BOLD)         if width != self._width or height != self._height:             self.resize(width, height)         for i in range(height):             text = self.contents[                 self._width * i : self._width * i + self._width             ].tobytes()             text = text[:width]             terminal.write(text)             terminal.write(text + (b" " * (width - len(text))))             self.focusedIndex = min(     terminal.selectCharacterSet(insults.CS_DRAWING, insults.G0)     terminal.cursorPosition(left, y)     terminal.write(b"\161" * (right - left))     terminal.selectCharacterSet(insults.CS_US, insults.G0)                 len(self.sequence) - 1, self.focusedIndex + self.height     for n in range(top, bottom):         terminal.cursorPosition(x, n)         terminal.write(b"\170")     (top, left) = position     (width, height) = dimension     terminal.cursorPosition(top, left)     terminal.write(b"\154")     terminal.write(b"\161" * (width - 2))     terminal.write(b"\153")     for n in range(height - 2):         terminal.cursorPosition(left, top + n + 1)         terminal.cursorForward(width - 2)     terminal.cursorPosition(0, top + height - 1)     terminal.write(b"\155")     terminal.write(b"\152")             x = None             x = hint[0] + 2             y = None             y = hint[1] + 2         return x, y         rectangle(terminal, (0, 0), (width, height))         wrap = BoundedTerminalWrapper(terminal, width - 2, height - 2, 1, 1)         self.containee.draw(width - 2, height - 2, wrap)     no-argument callable and should arrange for it to invoked at some point in         # None of our children wanted focus                 subHeight,         if self.focusedIndex > 0:             else:     focused = False