PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/imh-python/lib/python3.9/site-packages/twisted/conch/insults
Viewing File: /opt/imh-python/lib/python3.9/site-packages/twisted/conch/insults/window.py
# -*- test-case-name: twisted.conch.test.test_window -*- """ Simple insults-based widget library @author: Jp Calderone """ import array from twisted.conch.insults import helper, insults from twisted.python import text as tptext class YieldFocus(Exception): """ Input focus manipulation exception """ class BoundedTerminalWrapper: def __init__(self, terminal, width, height, xoff, yoff): self.width = width self.height = height self.xoff = xoff self.yoff = yoff self.terminal = terminal self.cursorForward = terminal.cursorForward self.selectCharacterSet = terminal.selectCharacterSet self.selectGraphicRendition = terminal.selectGraphicRendition self.saveCursor = terminal.saveCursor self.restoreCursor = terminal.restoreCursor def cursorPosition(self, x, y): return self.terminal.cursorPosition( self.xoff + min(self.width, x), self.yoff + min(self.height, y) ) def cursorHome(self): return self.terminal.cursorPosition(self.xoff, self.yoff) def write(self, data): return self.terminal.write(data) class Widget: focused = False parent = None dirty = False width = height = None def repaint(self): if not self.dirty: self.dirty = True if self.parent is not None and not self.parent.dirty: self.parent.repaint() def filthy(self): self.dirty = True def redraw(self, width, height, terminal): self.filthy() self.draw(width, height, terminal) def draw(self, width, height, terminal): if width != self.width or height != self.height or self.dirty: self.width = width self.height = height self.dirty = False self.render(width, height, terminal) def render(self, width, height, terminal): pass def sizeHint(self): return None def keystrokeReceived(self, keyID, modifier): if keyID == b"\t": self.tabReceived(modifier) elif keyID == b"\x7f": self.backspaceReceived() elif keyID in insults.FUNCTION_KEYS: self.functionKeyReceived(keyID, modifier) else: self.characterReceived(keyID, modifier) def tabReceived(self, modifier): # XXX TODO - Handle shift+tab raise YieldFocus() def focusReceived(self): """ Called when focus is being given to this widget. May raise YieldFocus is this widget does not want focus. """ self.focused = True self.repaint() def focusLost(self): self.focused = False self.repaint() def backspaceReceived(self): pass def functionKeyReceived(self, keyID, modifier): name = keyID if not isinstance(keyID, str): name = name.decode("utf-8") func = getattr(self, "func_" + name, None) if func is not None: func(modifier) def characterReceived(self, keyID, modifier): pass class ContainerWidget(Widget): """ @ivar focusedChild: The contained widget which currently has focus, or None. """ focusedChild = None focused = False def __init__(self): Widget.__init__(self) self.children = [] def 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)))) if n == self.renderOffset: terminal.restoreCursor()