j9KwXUw~5&ho4ClyMryXFW3}Xb9#;A3!Ozmvz~0q$ul-Uf6>q#qcRcW3dvK
z)ggz57R~@#w4h~=8>+>+)?>eP;B{a!q@PB_dJov;EoiB6b8_^`?vq*vr-8=;>w&yw
znF;5BbpJAp((_WPFBR<;$|zvv7*z^F(l1XG2C3l&+fESF+5~8h7)y5
zx4;+#Qato&Ag#l8v+Gy9El3ox`Gh2AZUsq?B+&rFXgc`(HtQrlXRD2+XNPW#Ln3Vk
z;;n3nx~?>n<{Nvpnv*J4Q3b!QKlgz8YD~fZ9It-R$azcu{ACGZ_sBpxn-G5WbGey|
zZFSk$Nrl>f^;o@|p}HINxuoK0%qT;q@M@mu)(vYSmVN?aTOu~q9peE-0h=9amCn%D
z4N4FFTbTV+e^d9&ia0HDzv#JKWrhp$}c
zIF-N1(y9MFFAi{-Uudi^;)MOZY3j#6R`vT=ia$@eZ#4LZuQFxrjn|7UsW?hc3G4Lu
zw{&uqFUe%eb&KYi#+u%^Hx?MzCTQopHHqt6-4J-@r6aXSSuZ-d6-@l_~O7GW)sy@wz2{%mR-^q|DSRis1?6c2}kLM((yx>_|=^w`dnmpn~Kn
zvwGLFEl~8)c{5^FW9GN8C$1l*H@wH62R62m5`TG36!H*WBV~;dQjzw*!>xZ{PONk$
zF=S6LvK(?ZW?J2#!Yqq(NIk2NX05j{>buvF!OGUhb4w9{Ry}#WHj77e*j_SNk7j_1
zqLS=&lrffj7Xxq^{$3gxBh^3?#a=RA>IRG64x$%pPas#Eb$YI5f1tFR^knEtMFh6T
z(ys`ZN)3`u@1P~d6~j}vkB?w&JM$nZBa1gb^&bV8Q`dJV$05fns|N-B$Ygk11%jDS
zI)$_B!UAz-L_Gmrq#UPo!^S#4Y7Q!Uxv~AITJSYI2?@@7HrHIQpVVuk`
z(ve_HGgK?+tqu9K@xx>Ax$zAi=2lKOT8rM`*Tf?S=(hs?>!YP^VSD8CBEMH_X=emJ
zN*W2Z5xF{-rdl=*m;jzl$WFD8hqr&2Whg?ux`IZ&XmTWf^+i6JP4WI{4*ef5^T5xZ&%!50aw_%C1D~XMXxpn?wVu_V%ontNgCU$~di6O4g)
zv!C5xj^clCnpj|oUAtejgFL7P*Q=3{Wfl$BI8ZZWQrMPDD*9$1G>P)CN}%_Ua-*&^c@)MDue
zzqH_2DfcJXH#?+if?wTG{tu(lq?P@bV(W*SQCQzjL^fl9hVtL1oB4)^1%OGnlNMbN
zdjBt~T=LHeAamsR{~bwO_%D$A$F7-zUwwOhbQiaY{n+vI<9B1cQKb|7m!J3--5WHG
zPtoKw+-=8F2>-)U{{34ngNW3OX2nX(5~Gb>2-BYw1Ht=&FHNLF>O=&Y4=5x-{mY@(
ziL%0f^LLT>rTx0qzLXDSNJymLvEbmO;t21#xOO-XX8rO|f%sP!r{Jmr&{x
z)k&<0xI{oi5zIvJFf5tUu+oAVXNaJbI&F-K<}rd^0;9maFSFX91O)Yj%%rjh+tx&>
zhdI&{jwmnZ2^W-gB?}VA5mu7`oP*^29Nnbd@wM+U(kGu5Hq+`G!sbB`Gb4ic)9)#X
zm`;XiKlEvApJkp1boSy-3FO=Rm`c;hHhWyCy9nZ^Z+D51xJX
zj-u+zEnd3q#m1w}@6!Qe3Cn9hyQ?l()LLuJx{AAP-&hxOPNLnXwasmrI!fv`xzW1X
zR~p3YeCRT}DiWa*_M%@3-07`
z?;aSWA`ewALZqrh_CN9<4&8}8Yg>zm)eDq{Si;yMAm%itGHQuJ(PCJso%ZkM*7M8n
zVx8$RFMaQaFoo^Wu=b*+o<-1rx!@ZR(x5
zfxfWUL>EY9$`r%l{*FrXGEgP#B1?N*&ll5tYWTa9yG`wS4zXRLwE~Nrb^R`LCgsSd
z&{A~?4p6yNNMdc|V&2G#x=GnPw!u|_MB}qu=9rT@au+(mxLAi_>)fSs={f7&A*s>1
zWSb3F8
z2IjzCrzwa=^zMPpP5RTt0+fpod3@3E4N+jZPK`mt>Px1XRNqJZCJ(ddTxg|W>Z#ST
zIzF4GFx0O&%IM@c>r_6W5{e^{Vw*!h(GffKZt56nf-(NNrf{fZ-0-ufUQ2Zq__#_kU
zyy>-{3v2$U@}j?l2Ai{H3x2t>T`uj@V=}t~T^k+0=7TM_9*g+);<)o%X7yc_Bn%z;
zJIX#gD9W{f~O=_sxpOr$;B2#hX|>O}m-J
z1ROMHt3{koXIToUVd;cQ8vWHFQQp&9v3*UR_j+D;4b^IY$MNk;g9}EBTAp|3*Z3lq
zW}GEOyts)q3Zsh~a;1YN4plsB8Q>xjM3FuddtN(=wV|;U^nf9{sU^Gy81jQ
zGK=e-(d0Z+2opl|^i;}owhhj;OVf&tJ{G@d-SFzD5|n&HPbuf<&zt~bNB(W>d|hhF
zAS3scFDL$%Z~J4KA&&}b>PxHpTt2I~KL~_Nwm$;Rjt#948-%{t{-jY&DL7EJ@u(`E
zcV6sg&~zV*>Di$8`rxN9E=%3XUsKq(;*)(*mN^YMdN%rVZGubIC%;RzzYWR6LQnEE
z-ngCKFahLNdvLnA5Ae*Q_UB=UUFEfIliAhXgRw>5#U)BV3>aY=bo{uEA^)sf
zVpCi3fksN;0Q+cSBX_Lh;)%dww6t}*stXwnYC-Kf|OOFiCd^ut{X`3
zSy}4wH&EaYW^LU0>=};Ut)N@@{0Kgzn$gtX44|wL%Ud8uZ78B1I%Ge?PL}MqUKZZf
z%BB%_UHOsdZP_S;D^5*Rf0=nSRo2qiwAP#4FldV!D3LKuEeB}2#Y5Y_)XD(XFf375
z^DxWi($g@d`ca671Pjk(MB~Qp0g|Px-<@821Y7Pt|iAq(XYz|F*
z6Errny1OOlD6jM&M=K`mo97#4QNDb&ATgs@y*iuPJ*go;oAuOkbDQY}efO)eB_2H|
z_VRPJSF=;c26N?g|4%dRji+s=_}wAnPAvcXk#LFPs+^6@E8)0@;nu{wUQ+>8VE~1_
zAoxadz3_R|#)j9#J7<1+*QM#rPl-klLZ2SM1uCQ0CywoiT{1LIhb&*Yom#Y2{P5p~
ztNFF{fb^GsOagMh-iLNB=RYhC_R{MP=8>{i*!sb8t~!V~a`-aH9@)iZTozz6aPmTm
z?miN_PnVf}mz+jqG;&e9pg>5_wPkA`=3b26`57z}Xd-m9vs4Fg0?*?DO;
zy8CK#r(t2+jW`HS*l$s$8ADNgtxkFV_D*MEY~EO|&bTwhIxLLY^fZ&^Ks-REXYqgo
zJlzA~Q*ZG&O0XPM%P8y_tAgG3Sv5To^9h<*jj&%ISQJP#^7f|3k&qWRx)5QY7M=RC
z2?7)LL3GRAO%Ic)65VsPtyK);M8%$-Pqk%hY4bJHm!_xl9;{>C`Q+q&Xh2(7c&O$s
zp-;E}J8_1i!qRKXOa)5DQ8y+kA7ven{f&Z}I8TM_W1W`y!QKNocW+|98L)JS3^WNo
zO@%OUc)RZ4oIGnEsG*x7vPgMP&{MDrd2D=;eLLp3@+LW~kZXFW(wSdSeQ68zt4&&W
z!kGj7%8fx_!@0%+$NCZ;keC9yT7q|m_rpvBJK6SGs&>GqG+(pB+ADYv$koeD!dWW3
z`WYW-FPq(L?Qy{HuVH8pY>8L|lFwQ(C8@W+i}vm$VA1{*G8m|9&W6VBV0rx_IMJo`
zFVCFw4wU+RS1d;KrvwF!o47G@(Wl?G;LAhv>?Qk)c|B9uxYoJhd)PLwAf8&Kf>WE(
zgy2<-L~Y6}i}7<%TWo=5?ff@wmb!?>r%Ovw?nJ&|hQkEjy8Lxaz!hsE
zNaEcyAtJ^WfK7E5+pM#ycoAbHqmENPeogHVXHj?g@QraeIWSm)o&q=Q<_$+hjoUtt
zO!6kf^X5WfKTps%Z_QH~!JLLv#%RknuZM8wI|cKtXczI&v_|@4^zW>W6}Fr1S`qy+
zC8c*!8walZ3Hed)uARU|vbq|o=$7hU%SRQ>mBqgfL4p!*#irdwbaFARFgW|R^r)P9UYID!@!en;tZhLW>FhjnzNK)I&kd1zS;fWqxs4LYZec&QKSw5F6w2pq#PwZvp(h?NUurV4hO-hEd{XP(B>B;`R4i
zb4XGF$&%%Pa9v1~x)dJ2h7C+cb1M2LgX^^heZf}1}mRWE5hQ07Bn3tJaP1LT>Rhr<#4GHFW
z!>@T$3S)-^uZZ!NzUj(LO0dN!0X%=YJ0k$py3c&^9L*|(X)5?4!E@pUQb$05OtJvV
z=ywFN^Z3Z9h8hRt)el*35rh}Lg%bf`;VeC5?g{}Z{T*g@!M~zJf~Q8mNH605$hmh!WaJd{Bq7t{KPfJqv-3jWQfm++%5*25|1DX!xGL2$h`esk<1zh4mpf$R#9
z4H>R@XZq)jkMZC&cL_~qb}RwGV~vmkgwvU;ZRU(A-O|0|nheUM^7UJvm=D6*7dqnP
zlHTuyGWHH_UmBJ04sX5&Om+DIOJ~{$kw8(81Z}-FOx^F1ADE##Rfb}gsKo8$eOV}P
z@8q_>kSo9j6nG1zaPQ_}xX|bg~95R92e@a*vN
zHZx0g6Y^tz!AQd2xJ0hsk1f#ACVN@Y61hP%C3#oI`)=c9;3F68=}ppV=SZyAUJ+gR
z*Fi7s`ADZe`Ej%CjoiXof0j}c
zHmlWxi|h0Kr28CsqPWX97L%U}rZ9y$v5z;Z%Zy8HZtLV|`U+TftCml=(%t5K
z5gb59GpkOq=yf@pjFj7Ty-VS!Sn@_AR+ue>9tgP$<=MN^qW)4B{UT%EZxYW;*~>@s
zCSpZ{sR
ztl}O-iFw#&?=`n)ueg^5azf9se+sz=Z3bFd3yv#WgWF`!_C58E>^l_;
zY6`sd!vxH#;v72S6Taal!`i)#O%@Q$SVHX#BR>gnM*@k?WbnR4mUH=pTu%%n>@>ax
z>1e#&Ey#<=s9P7#F|HM_#k7U($Tg@BylNFt@xDAIxz&ZpR80^VD6s~qjK3elgvp$0
zW+=!
zT;8CD&`9_`6x=yns`KcG2X7baJ9-YNrC?_VkVA{dMy1jMdMJ->Fc+j`baj8iEtOX7
z8fyV^x89}HX7Dp+zD*w8pOpbmfjnXI69TYP#A#Tu*xOy2pbSvLoAzG$Tv&A+NCL
zz>+QB!?P1}PSD5-LVvciRt(z*&r^IJb-8*uj^7|hv(hI2gb}*HePE$uBjn0iyDM*7
zC1wweX&LsJPG=j3ba)PaaFj9%ULkw=jQnguK_m8??31a(c3@SmA$YG+@s#uyTWWlJ
zS&+ek_yA@&2++s;BBaQPxJM&8UFM0-inE%mfo#02h3@9Oo%tZu#OPc2Q
z;upJMoS^1)@S4otu&LaFOOjhCO(-0BPd`%;Bdmi=qaR1Yz;$|3
zDi7v}$6f7ibrvRxIep9h&@4UWJmqtSi!CBniRIGDkXsZtXMl?rn|COhwuHKDvORcU
z^|vnPVC6#A{T2JkqkdyW-ngTcX7!=0?%$lB;ff@;<~DPvfDoH_o@adn3DPbMS^2yo_4dCYp9;&id6zv88U$gTrrtme8Y7
zrp>3E1vOu@zr3rwE9RIJanYw+O}Ahp>G@4pdwYv|ze_e8(@s57OQGn=xVJk-*Y(#%
z$I!)wDA7(Y^|HJIFB5CWDfCgexw&c_1MgoRY%Yw3tobhw7F$eVI}^oMb{1a*6EJU6
zCJ*4*|2(xT>k8dm+S?f$R@>R{Bph%y?B5|b0u+-_ypVmAf7FHkbw!5ReP>w<0GyG>
zO;cz3rOH@k39g01#n)qH_IC~{BQ3fT^=663UkEf$ZDGg*gXy-)JGu2sG(jEi(5pTT
zw)ufzA~PDFl_W_>kw+^0!AdvzgV(6Zs{P>9^%=|hU_d8))0RHao#piG&$RqTV=JL;
z>uvT{YZ^(9w3>pwL|__{+eGAy(uR4S){V*C+zbUbtIJ}yLS$q_t=3jYIqrH<;hl@?
z5trMFQ&H-{=za|AK42tzFPfcEhBZuooMRZTAoY*G_LHNIGNCjj);mTqnfDx>qWmr37ele1qB7!=ra@uS!OyJvfPYL2nQ~PUyIT33)|$MD^sdBs#IdgGu6Ev=08#yr!{?{;(S!FF^;zz`x;BnsMkMe
z+nGIXvZ`)ztbXS<_C7R^Ney|tIaxc{bCcS-s%)_Irc6gm3t#pltf0b_CpJWv!Miy`
z2~JGGu(tn~hd4>OM$IQH)luoV@T~Ngw(rGDh`>{
z+PvVa^06`e6T(z~S<#BDC2^GLA2*P8k}O6jO@sB`f1pFO8#h{ij$(4ND`qp_nB8ds%j!AIAKD&5#e;|btem>mt>j&
z*hTi2gH77wcvBNucUqala;8!Q`YZCxPJt$uoI$jE(^KOI@0A>euT!HXb%b^E4V{r{
zqPgg2Nhm`Kn9gb2MW@c(Zh8sVoR7T0gG`Xy&n7B;9{_vf;Ma_`{>d8kStyeX!m@jA
z;Ev>#R@7CE!`UbI0?6qW)sdxn`N{52B#rs}LP#3M?g}o=ESVhY$w@Q%^!zcg#TdFn
z;*%i*u#PCwydzep=Y;0Jv_@l+x}ff*zIW5)xUPS`4!L`;qdk5j@e5UEp+CUmcVCJ4
zw+Z_OuVF(K-~PgwG6m%57hJmi5F2XZd4imZ#du=hF8AKM&XyUlcY3^u82lC>aIHp}
z+@Ab|q+$^I{)
zFy@mMUZo$HTGfkyX1wzgHnQV6%H!*1Y$HoRr2_a|_{#JYZA{_7Cp@*hPBfytcW6c<
zcN#A2(@T5o%2-#|Pe9PNH1!Ud)2Nb#E*lf*mD-FHJIz&uOVaCdRtOG<#^+GZ1KW=
zVR@z%0=7X0&fVi(S86UYu#O9AGkkg5R7A=m}SYm6YX}b(+0t6}Tu
zPgLaA;;;$#nATzWR4I2$KtvdgU6n%IT*u=#8ayUr!vaK7Qy1ECKPmUO>D5$rL@Pk
z_cViwt3~zlKiUVZEBrHB#yy&LJK`My*%c~Ciu5tYRydIr*|{uT4NxET@2Jh4&)cW|
z6pOvFf{G_sZgJhvuDWJ))ISJA=T{5p%}yuM78(^gz*rm<_fy&{j2M{nMlA3kcS~M7g)M{8d$obx{d=`7I(6+j(t`|vj24|!-q@;Z
zD4#3+V$3!T6Hw7UA(ck04j>i$U;>E%iy
zem{r#F52z^WL~A&7xyXc(97&5H(UyHswO&-*~J@SUAG7C(*Kd7Tss#kF(Ddf_re8Y
zF6GK^MC&8=TI5%a{ps)D50h6hF)5MfCVYvbuFj?C%eerYe*`dh-jsSO1@mtbkmcKQ
z%38GCA#+Zh0LS_$9S1YX1TKD00@b#uEiLsO=m`S48Nj-Com!a(!KPa7iaB|3A2e+J
zmaTM@`AZszcAjb6q)p>iZ%{{x%f42DbG5w*zfmIcQz}RtRr(4ozAwGh^eIi0#d4
zf64OXWT*y6g&J0H^+giDzs3+U6Z06d`_x-;vszy!cZt|sdcsmvLuQeZ)tEaPzO${N
zv{iSwMCZJav_j5`o8DHjdC>bZ`a_S};-#~^9jKdy%|!l}#5Mctf$AYFb>
zhS2B5N22Y^XBJFnuG4wtli8|wr#=-t8-Lwqxj)jy`540yDrp~nwv-yV1vg|ui<^mS
zu+2|(&-XnQOT1K*AZh9=ATEjN;C$tH9L69qnA|PXRBsWkL<~7%KI{4ZzGy@Qb6_J1
zi0`wtRTwRyVeEq2ihJpEf(y&?a=}kPNTj5O{YC+4`&L=f3Ys%{b+!+jve#)ML?O)`R7bUK%NG8$l8!7_R>(-562|u7XP>P;op37
z>(A*~)ROuAdxE2Rh@T+NQ(zzKj{c_Sw{q4L
zacj4%yxE}-{>NKHa;}kH42swB7h+QU|8>6_v6Mn)YbYzJ_&fPyik96bnaph6n!jrX
zciJh|?XZLdk30Tp!|-}L?kB9h*K#e^lYqKru}Yei%TPs0Jw+>Z!V~9-?YSPasG@i{
zN*{nM^9n@wFL18-Op2nwLqUQ|_%boR_%D|QV?h=e);2F;R
z|6v7uMv&BR?ZL5UQAirZXJK(d`~T=VC?+Fda^@PVh+1)>Q7bqbht-WSD}YOyM!D?(6_WNA0wZ>Jl_o4Pf#U_id}jO8Es0BiXGRyd?<
zk!=TZsY*ecqrHb;;~Nva`^CjZew}g1p3F+u|}qUi$-6mt%Aojdid`_
zsRf9~=FHqm$9vh;$fb)X^8e`TCD8kE4O1TG8-0P`199EVEZrM14vO~)w_$F`UYd>p
zt3*}748U2_ojA(4VF2f5tZS&-O}%YUYObM)7I#M9_C`Nz(MT$GI_5-vk~0TcuQaMIAETEA=bGI`nB>iS7JwfrbASFDq7kIzIuXY(-?
zuhAZd?(2R>*Dlj`@I}vSf+Vt@ScIaurae34mNjJyngUOH+VjV8FDKywQp78T{<*W
zbrJBa*wSWZaE(5@5Bir3ef{xbD!4bs>6>W$&R1dmoCsMFU&*Fuyc_9WpMWk4>DPFZ
z)S@y;Zl9G2dtsDJ3O>3yuh;inDngGzw&GD(zxsho7nRPF`0we9dIASXN84&1)2Iv)
z5|EPD1pCZ};%uG``I^E9G%BJ`)ALvu35Y#!f=~7yz$$1uM4yXwIT^U7PSmgumw72I
zV`9Fq_|y^O9#TE6QWr>uGh)D&7^}>~CC>Mv>g!#cUS+fbI#N?DoYxB6yi(rM(V-Xj
zC*Q-}FhZ}JB?krLt7NCoojpFnISi+j^C8Xt^A*3J_d|@H(bQ{RkkdRk2Nouk=5^^^ao>0iOkMS`{Pzk&MoLe*+M(!qb8`8=2L>)OatlsVC1!}y7>
zv`yzN=xI}=^J-psA3mgskCirSr(J*9W*!{NGoYAZQ!^dNqk4&
zjC`aVS2(<|Mn+WtQMx@fc%Ty0w0K
z7Ypqi>yb40^}IQ$g-NMp
zfn(l-3mN5~Pd|70KiWvOW44;)J@S0FBKYc2-dIVX>xPW7%ph2`$(ViS4}=>R`U1RP
zCjpUH9oGM?k$z`SVp9)u_{GS_{!oJrdeR2BJ{--vGUJ4mK|++AB&G>FW~9qxw|Xm~In-Y_+&UOeV~+L!Rt}#5#5>Hso%FQ
zpbSuMld7Y;0D19yDo^6&=0;^CWsK*B7Q0tOTj9Z%=lKp#)I2??GuxLMW(dLQn(2@Au(zbC!Um`BT&
z;#pD=!@H<4hmL6%(~5V;a>6$EvA)A{sG+KOqoY_Or5x7@;+-AA9!M7U(>C)VgOdjR
z;?Bp88}>l96xY|1s)d{~0BQ?$%u9p2wH6ILmo!us;v@1o=QtOm>zsEjfNh`}>(2Gn
z!%N_F*)$A^n9J^)l()1#o}Al3Qwirf#%R=HVj+2V^In##;qsP7WEOe-R)%a9cyIXx
z4LOYGjkcgjx4>NnIZDqC#Ao!#ZgYA)9Yo|W#KfsR>Jf@u;wtp8&!zv5p)%fKZhgm!
zoJbfPpiN-KQy%$5A?tyVfo6M>TVS0Wg-x%m!atLybEuG0OBURNz{0ry%(>aJvXLhl
z@&1())2e4yC*45x86Pd*jtPg}%I%JA=dlw{$dDaH6{3}I3EgMOSNrm7v`4C%Qcs=y
z_;C>r4RYo?8Qpzv3(aOz%EKy`O?OHf#Yh7326QYn%4+qSmiJ0ai)xhSXwi
z?U&dBw@tcrhwf^}3D^BPUeDTqf#81@@e8Hd0R_+~_A=)!Kca(xAd)0(V0$^Ku*ZT=
zjG4w2cbnNSm%_+k^-YyC!}=UA?#dmGXI>VvG05p&8c4y+y`^ESaZswkR?&zE{n&+>(|U+1OG4f>Ax(y
z2T%&RfG(y2L+YnA1hQLOA&+4AX0a$ILR*x{cJQkedA#8jI2$Linn^EaEZYhdX;;i%5-%+J339e`J=Vv?K0{
z&dWZ=JeH9(N9Kn(^Yb9^aocH4ldWCHQg;-}_LkmPiCB8yc{R0jc=+vW7Zw8plEv!G
zXrL!BM|EOP$Y$?ZV!6fl9s#C>Kd$;rvpc?8)$6z%ic{Y;^gW4v5ex9M&@%dJq{OE8
zw6JhAl}Y&F2>>Q#R!GY40oek0j1$X3XyyCq>kRQ2=?*!-X8P)`+@5l4IRu)4oGt6R
zyLxV|tQ37g_fs$+G8He55Mi*NPRi95L!PV|BgDEb>ZZ~nRw_HpZOd{#ntGN-1aGet
z)?hegfX0UbqiRgonO$E0$Ro@xWUe4pZf;{$jXF`JsPH|!+aV=UU)RDMi`Y2xg$poY
zTBRAEGq?8mNbah73lI9o{alI}KcIVJf{*mFx-}3v=rqZ^??6J$U!N=BIJYsWJ44*W
z$3RJq#Jmo7s)T@peU}x=3Mm0I@7(IUIY>q5imobh9V0W*t@#y9x%5%>25aZfftJFS
z)6L6crtzluU{Gh?lHt&e$ddP^buQbBzFIZ`Y4?Md!q44+q_9@(kCEjt#93jDQe8V>
zRWYweWG#DAKwj_6bW?Q67mk4BuCZ^lkAvB-@V?O+*VeKfxT?+LKbDp>>aRpX#o|Db
zL-tAXoUk0p^8r)Ad+6JEIFNg^HOQcoxSw6Cq~2Q{QgvuVoJm&H1LW(7->hBy6ZH|S
zoySuj2&?yU`k>e8A3-C(;YNBQEp&-9?*d~x{vmujfac2NA~moccSacSeOS!CF^SIQ
z^QD}G&l&KwTBg5lagZF@+|B1_UfUJZk&t
zGhXb=C2GLz@B6P|@o885{T28*tk=kxhx7KY>qk}S_2unr-o_`y>mu-M27j}Kw4a}S
z#GjRuvzYV`(k7b|M2hNe&dx6Ss427hIq~D*?B49Y+3ov?slccarqc
zGIM)8KeKszbP%nu&4c0tmW+bYT~saz{=6-c1xV^TWE^i)hket|eGD`ogpZ`L$5ke?
z01fI3n^mhk90p}v_M;_joVB*7{0%Spxt6f<0n9`-;*ca1=2kF!s3qe
zpE_f0SVEFt6M5{=E7}^dhR@(v_u}=bR%w9rO)gNq9(6V%^GoYpo(q%7eip;g
zSMN^}!#@WbPLc9xxAX4EN8I5xPErh}mIE3xvOsWktteKt9EG480(7_J`%6oCEMmoy
z!8LRuc27TExs7?|vo~UiZja&Ip#D<8C}H|*%o8NzzV*tj^Ak)MJSmB9LAi}x>e+uL
z_@HCBsZ*ez*QiPXCJN35>e5Jhw{bZjjelFr6y#bk#lyhSCUPz7^KDMWv(sZAni{H!iS$Z{yLJHh
z16d&rmwrjkL#0=kazu)>S7JY0gSSDGOc{^MdIHIaTTkP7l08z9z>28hj&_||nnUST
zOuP@X^7yCMfx3H6MBNuEUK{LB>9Z53Bi#N}jMf~TPqG<6hxDqPrMrQ`^}`1KhZC!&
z33I@WJ2C;@X3)h|0d!jAdLClh%@VMC>tk`4g%0?U7f?ghfziymemAbI;z;Gx!okcptBewx>u*OhA^BwM%Vu#GMv{btfGrsknja;f13CWy%)
zYU;66uRR)|QKS8aCGeeSxrE?}YB$b#87y4#cDc}XPJG2uBY$DF;phFK|itXyETLKMCKeGGe3OS7o=+-$4RhE{D
zhwsKC^}sdTz#Si?lWyF&u^gjoy#1_rY_V^Rgwaz|YM%p3MkCO;s+g+Q(wit@+u12@0|!*+p&>+A_US5@)$wCVeXD&2?0|8K
zoLQ@t8!)CNlzzjDjoYLy$#K3lT~T=M$vN`uzmITWZaqgkJUro3m8s-2U?_Q;bM^TO
zhPD4ZoO}3Uf82=zFvj4G+I{1MF7P4J`rYnF!>my;)eXDbONr*~%_0Xv`8-q;8aUCyT`5a3H0W2O9bPxE1wGXZo1
zI~u1`NhoSH*ZyD{!GVm2U+!>6D(C8-PcN*EbdToIv!=iIX)dME>8qz^45WVdGp^B#
zvzt=?I9V^SBL-J4+@T40^Yq*(BheCW8`>t4TY~xmxCkYN{|8$-bPF(bT+oMjyW(x|
zB&Ns+C8HWdwiNXyVzM=48xc(+BKpzr
z6CooCBiWM1Ap0(&1<{DezNM&;ecxqCy!V;$)AIV~{e0d(UjN~F?sM-s_nh;+-*fJ9
z&=;S5qbE1kuEx_d4HDwxKR};?_6POkBiSF&xnbi*3RB(6MOC&o=UNVM(rl_&Vg^`N
zu{S0i1UH7Mz|on#hl#ocbWeFV*=M?PYv&90U9xv`FV_wq9uw7Ro5K~0^otlRpIOZ^
zEo_ldDM0X|ofBvZF?9uwG3EL|osCI^ePtc85~=~ca2e^~kwn5(gm$&~u;rS~E@Fmr514tG#uZGcsEYk3m?t3}=}x
z%GtH25ajM1FDFcOm)nTuNMutM9(4tuoFOH+gwWjNfVAg`oV^|(g3N0{s*=l?mg7`P
zRlC_7C1Zun580!5ihHzHY+ldr+;RpE6cWA9laM19sc{AEKA9ud_7k@;xbx1i!x(pZi|EOGz>5-%(kjg1S;1u~6Zft2LOICDv&$Q{`eulW`|ye0)9cAJJe
zbVwQysIu=UCg0
z6(b3d_iwp!B;_^SnE8J&{ZNqI{b2exrL>_XRWH@zT?pZm-SbBG`7fsp()1rQZQ1s*
zpX2#P>r>a@ReI{ra|nyVkYd4=5D@M5I4feNesFOwuSfCZ745*d`z~)(ks>VhV7k)$%9O-=*
z+LCe>C2E-Q5ce4usl&FBOjXDTiyYpfz6T~n=2%N*k&X1UogG}oU7XIlx#+0ixpExT
z4+hw@X82jbdba+#HA1Z=i7YHEVTzs}8W9o#pe!!Dc%hr^!<=J_SkS>8*p1gNNnAlc
zl%{T|^n@_FaVfssP#hu`VezILiQxZi)BU$LQ+Cz$7Wca8L)LMUmh-|BgnpX&ANHZv
z!&DSZ_b$H~GEq&4T>x?oA|TlOX0n1I^mlL?e;B9WUE!t$hHJgw!<+svL%5J%+iaAA
z6YC35#t+Sl;UbMd=2Qn>Btev3-NF#ZGvm%j0;2#Iq8qsOU=Fx*epeR!1tN>V&q){hP$rWh$T_`jgtxMgl!BWmY
z2LtmFVJ5|L(+Ot=P6Rm3YA~X#ndwAo&-B3=lRuJJP)c4!Wqh+cO!XelD9
z8c6C$Rv~t8k5;gd>OpWIyTgQ}q7(H7B=RiYfhK<7i2S5{GEMt-~OTu8eZje_LS|=_m1k+CdGhMdED^
znlapY3J&DNr)bzZoo!rKG^V<lmrYrn~WOV*$Zt7;9RGRw?&?A-65fyj|jV#+cvrQ
zloIk901+TkE|x`GSoKR%S8)%IwNEuX>+|(U!_k*<5!U0a0kTd#^6egzq(?%o
zOUsrDF3ShQ?PWiuZ#xAzgeDblMA%>x&};B;U@U0}^j3g6D5A~!vJp4q%Y~NuJH7QS
zdl`)jCs^I(R^aa8f8Fl4FH~du`@C*EQEz*F(~JD%D>cKRgzg$<(N9;SaRR)mw#|xz(QXroBiSV#jv4rW
zKEHjs0xn_plwvkk6lfEusAaH4AhzrWZb&AACF58h++Av!v$mZOyz{EIw0E-{C0>ND
zu5n5xBIW7~v)LSy2n=rjuE5xP_$nA1DK^N?yFl@MGSSDsq4&zuD_=vxXP5Z;27A(C
zT*F1q=5p0WJ#Dc1mlp#H4ly1&!o3!-LhUbD0}Ow{M1??{4HHL0QEi0v8!ubgYa^Cv
z3I|129#``}Bi}qPE-YhZ+a%+C+7$CdO9rS);HM)`a7Ce|lI89AdTSyBz+LJ7ju@G1
zEMGiVnanJc>K=|5U~t-k>nfDzr3U$q$Gh$b(n(x#yE5*p7d9yEHDWrM()G|_VDN|6
z_Y`cYQd-NUphrSgDDN3leg!8#jn&O4c$_zg^o`iGBSh&W&8MH_)W~w;iRz&khd1m;
zQifudOJ3F#u(x(rsc&23bX1kGm?zZ>nX_gwuk_sq8Ph)79jR^m>uls}dGaCMz7F*%
z|16j9Qx(>2Sz>v&J&jAtQ)eQE5-~HchNYp+&Q9|_Ihk&651qajC2P6FuX!GKLjRYg
zC|zz$vzs*!otr&Z@=AXwhLU%A!eHr1h1rthq(@rQl_RxZ2*D2uwYqe90-Vn$`ADE#
zR+=Tf(FdgnEOIRq;FDlb)pDs54MPWe*
z3k|s_?LFXnNOt~5CGLvoz4F$lWszx
zpWV3l@RPK~r2kaf<2gQA!+KVb*15OaZFKc~#aNQ~gxF5_y5v@NQM>1?n#bb@hRAKW
z)cMVE(Ib8Z`?fnd|LAO-^W4HS_5qP^qB+|(TTSR+wsg&UtDmi~6;NwxOepJp27FYb
zUp>OY%`fNjeoUTQa(rvBWZuqg`RxS7Lv_l}wxm6;THk5da_QlGve{#{h-}@J?EP-<
zFG9``3LAg;zsc}DR*lph1J>VzXY$4-<=dSH`Rx{i-XtFqaY<}w9>{KpWW#~PtJv%`
zNAs&;$#pGK(I9NBJE)J&gRDzk&tK|~l83@dmbf@B?3)_Jg@!ITN^@t(`-mQnJR{(n
zIhpq6XHbggT4NoB_Id{5(Ho8oCSAsKxtS@Wa$YQPoi`LjmjN@Kx7iL$x%6%+)x
ztFRM_DJP9U%>3ULW(Qq@TULVDPUyi~NFYy!E^yNL2Mb6-q2F^o*(&I(eh<7_
z#?xO9i=nSo5bn=Db;e8(yvW%JT9?p!#-g2pR2bl}%(XBM%6?p@q=`jq?P3`O=i*6%
dtLwjcEO-W;AeBTsGyM$BLtPcClBJ9%{u`q-7Lot}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java
index e05468951e..8bb047e2f1 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java
@@ -23,22 +23,22 @@ import java.util.List;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+
+import com.google.common.collect.Range;
import docking.ActionContext;
import docking.WindowPosition;
import docking.action.*;
-import docking.widgets.EventTrigger;
import docking.widgets.HorizontalTabPanel;
import docking.widgets.HorizontalTabPanel.TabListCellRenderer;
-import docking.widgets.table.GTable;
-import docking.widgets.table.RowWrappedEnumeratedColumnTableModel;
-import docking.widgets.timeline.TimelineListener;
+import docking.widgets.table.*;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
-import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsTimelinePanel.VetoableSnapRequestListener;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
import ghidra.dbg.DebugModelConventions;
@@ -333,30 +333,24 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
private final SuppressableCallback cbCoordinateActivation = new SuppressableCallback<>();
private final ThreadsListener threadsListener = new ThreadsListener();
- private final VetoableSnapRequestListener snapListener = this::snapRequested;
private final CollectionChangeListener recordersListener =
new RecordersChangeListener();
+ /* package access for testing */
+ final RangeTableCellRenderer rangeRenderer = new RangeTableCellRenderer<>();
+ final RangeCursorTableHeaderRenderer headerRenderer =
+ new RangeCursorTableHeaderRenderer<>();
protected final ThreadTableModel threadTableModel = new ThreadTableModel(this);
private JPanel mainPanel;
- private JSplitPane splitPane;
HorizontalTabPanel traceTabs;
GTable threadTable;
GhidraTableFilterPanel threadFilterPanel;
- DebuggerThreadsTimelinePanel threadTimeline;
JPopupMenu traceTabPopupMenu;
private ActionContext myActionContext;
- private final TimelineListener timelineListener = new TimelineListener() {
- @Override
- public void itemActivated(int index) {
- timelineItemActivated(index);
- }
- };
-
DockingAction actionSaveTrace;
StepSnapBackwardAction actionStepSnapBackward;
StepTickBackwardAction actionStepTickBackward;
@@ -464,10 +458,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
private void doSetSnap(long snap) {
- if (threadTimeline.getSnap() == snap) {
- return;
- }
- threadTimeline.setSnap(snap);
+ headerRenderer.setCursorPosition(snap);
+ threadTable.getTableHeader().repaint();
}
public void traceOpened(Trace trace) {
@@ -508,7 +500,11 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
protected void updateTimelineMax() {
- threadTimeline.setMaxSnapAtLeast(orZero(current.getTrace().getTimeManager().getMaxSnap()));
+ long max = orZero(current.getTrace().getTimeManager().getMaxSnap());
+ Range fullRange = Range.closed(0L, max + 1);
+ rangeRenderer.setFullRange(fullRange);
+ headerRenderer.setFullRange(fullRange);
+ threadTable.getTableHeader().repaint();
}
@Override
@@ -524,35 +520,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
return myActionContext;
}
- private void snapRequested(long req, EventTrigger trigger) {
- long snap = req;
- if (snap < 0) {
- snap = 0;
- }
- if (current.getTrace() == null) {
- snap = 0;
- }
- /*else {
- Long maxSnap = currentTrace.getTimeManager().getMaxSnap();
- if (maxSnap == null) {
- maxSnap = 0L;
- }
- if (snap > maxSnap) {
- snap = maxSnap;
- }
- }*/
- if (trigger == EventTrigger.GUI_ACTION) {
- traceManager.activateSnap(snap);
- }
- myActionContext = new DebuggerSnapActionContext(snap);
- contextChanged();
- }
-
- private void timelineItemActivated(int index) {
- ThreadRow row = threadTableModel.getRowObject(index);
- rowActivated(row);
- }
-
private void rowActivated(ThreadRow row) {
TraceThread thread = row.getThread();
Trace trace = thread.getTrace();
@@ -569,27 +536,15 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
traceTabPopupMenu = new JPopupMenu("Trace");
mainPanel = new JPanel(new BorderLayout());
- splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
- splitPane.setContinuousLayout(true);
- JPanel tablePanel = new JPanel(new BorderLayout());
threadTable = new GhidraTable(threadTableModel);
threadTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
- tablePanel.add(new JScrollPane(threadTable));
+ mainPanel.add(new JScrollPane(threadTable));
+
threadFilterPanel = new GhidraTableFilterPanel<>(threadTable, threadTableModel);
- tablePanel.add(threadFilterPanel, BorderLayout.SOUTH);
- splitPane.setLeftComponent(tablePanel);
-
- threadTimeline = new DebuggerThreadsTimelinePanel(threadFilterPanel.getTableFilterModel());
- splitPane.setRightComponent(threadTimeline);
-
- splitPane.setResizeWeight(0.4);
+ mainPanel.add(threadFilterPanel, BorderLayout.SOUTH);
threadTable.getSelectionModel().addListSelectionListener(this::threadRowSelected);
- threadTimeline.setSelectionModel(threadTable.getSelectionModel());
- threadTimeline.addSnapRequestedListener(snapListener);
- threadTimeline.addTimelineListener(timelineListener);
-
threadTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
@@ -599,8 +554,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
});
- mainPanel.add(splitPane, BorderLayout.CENTER);
-
traceTabs = new HorizontalTabPanel<>();
traceTabs.getList().setCellRenderer(new TabListCellRenderer<>() {
protected String getText(Trace value) {
@@ -633,6 +586,32 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
});
// TODO: The popup key? Only seems to have rawCode=0x93 (147) in Swing
mainPanel.add(traceTabs, BorderLayout.NORTH);
+
+ TableColumnModel columnModel = threadTable.getColumnModel();
+ TableColumn colName = columnModel.getColumn(ThreadTableColumns.NAME.ordinal());
+ colName.setPreferredWidth(100);
+ TableColumn colCreated = columnModel.getColumn(ThreadTableColumns.CREATED.ordinal());
+ colCreated.setPreferredWidth(10);
+ TableColumn colDestroyed = columnModel.getColumn(ThreadTableColumns.DESTROYED.ordinal());
+ colDestroyed.setPreferredWidth(10);
+ TableColumn colState = columnModel.getColumn(ThreadTableColumns.STATE.ordinal());
+ colState.setPreferredWidth(20);
+ TableColumn colComment = columnModel.getColumn(ThreadTableColumns.COMMENT.ordinal());
+ colComment.setPreferredWidth(100);
+ TableColumn colPlot = columnModel.getColumn(ThreadTableColumns.PLOT.ordinal());
+ colPlot.setPreferredWidth(200);
+ colPlot.setCellRenderer(rangeRenderer);
+ colPlot.setHeaderRenderer(headerRenderer);
+
+ headerRenderer.addSeekListener(threadTable, ThreadTableColumns.PLOT.ordinal(), pos -> {
+ long snap = Math.round(pos);
+ if (current.getTrace() == null || snap < 0) {
+ snap = 0;
+ }
+ traceManager.activateSnap(snap);
+ myActionContext = new DebuggerSnapActionContext(snap);
+ contextChanged();
+ });
}
private void checkTraceTabPopupViaMouse(MouseEvent e) {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsTimelinePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsTimelinePanel.java
deleted file mode 100644
index dacb0ada5c..0000000000
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsTimelinePanel.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.debug.gui.thread;
-
-import java.awt.Dimension;
-import java.awt.Rectangle;
-import java.util.List;
-
-import javax.swing.JScrollPane;
-import javax.swing.ListSelectionModel;
-
-import com.google.common.collect.Range;
-
-import docking.widgets.*;
-import docking.widgets.RangeCursorPanel.Direction;
-import docking.widgets.table.RowObjectTableModel;
-import docking.widgets.timeline.TimelineListener;
-import docking.widgets.timeline.TimelinePanel;
-import ghidra.app.services.DebuggerModelService;
-import ghidra.trace.model.thread.TraceThread;
-import ghidra.util.datastruct.ListenerSet;
-
-public class DebuggerThreadsTimelinePanel extends JScrollPane {
- static class SnapRequestVetoedException extends Exception {
- private final long requestedSnap;
- private final long newSnap;
-
- public SnapRequestVetoedException(long requestedSnap, long newSnap) {
- this.requestedSnap = requestedSnap;
- this.newSnap = newSnap;
- }
-
- public long getRequestedSnap() {
- return requestedSnap;
- }
-
- public long getNewSnap() {
- return newSnap;
- }
- }
-
- public interface VetoableSnapRequestListener {
- void snapRequested(long snap, EventTrigger trigger) throws SnapRequestVetoedException;
- }
-
- protected class ThreadTimelinePanel extends TimelinePanel {
- protected final RowObjectTableModel model;
-
- public ThreadTimelinePanel(RowObjectTableModel model) {
- super(model, ThreadRow::getLifespan);
- this.model = model;
- }
- }
-
- protected class ThreadRangeCursorPanel extends RangeCursorPanel {
- public ThreadRangeCursorPanel(Direction direction) {
- super(direction);
- }
-
- @Override
- protected double adjustRequestedValue(double requested) {
- double rounded = Math.round(requested);
- // TODO: Also remove 1-snap view buffer?
- // Until I figure out the event processing order, leaving the buffer
- // prevents some stepping glitches.
- if (range.hasLowerBound() && rounded < range.lowerEndpoint() /*+1*/) {
- return range.lowerEndpoint(); // +1
- }
- if (range.hasUpperBound() && rounded > range.upperEndpoint() /*-1*/) {
- return range.upperEndpoint(); // -1
- }
- return rounded;
- }
- }
-
- protected final ThreadTimelinePanel timeline;
- protected final RangeCursorPanel topCursor = new ThreadRangeCursorPanel(Direction.SOUTH) {
- Dimension preferredSize = new Dimension(super.getPreferredSize());
-
- @Override
- public Dimension getPreferredSize() {
- preferredSize.width = timeline.getPreferredSize().width;
- return preferredSize;
- }
- };
- protected final ListenerSet listeners =
- new ListenerSet<>(VetoableSnapRequestListener.class);
- protected final RangeCursorValueListener valueListener = this::cursorValueChanged;
- protected final TimelineListener timelineListener = new TimelineListener() {
- @Override
- public void viewRangeChanged(Range range) {
- timelineViewRangeChanged(range);
- }
- };
- private DebuggerModelService modelService;
- private RowObjectTableModel model;
-
- public DebuggerThreadsTimelinePanel(RowObjectTableModel model) {
- this.model = model;
- setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
- setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
-
- timeline = new ThreadTimelinePanel(model);
- setViewportView(timeline);
- setColumnHeaderView(topCursor);
-
- topCursor.addValueListener(valueListener);
- timeline.addTimelineListener(timelineListener);
- }
-
- public void addSnapRequestedListener(VetoableSnapRequestListener listener) {
- listeners.add(listener);
- }
-
- private void cursorValueChanged(double value, EventTrigger trigger) {
- try {
- listeners.fire.snapRequested(Math.round(value), trigger);
- }
- catch (SnapRequestVetoedException e) {
- value = e.getNewSnap();
- }
- }
-
- private void timelineViewRangeChanged(Range range) {
- topCursor.setRange(range);
- }
-
- public void setSelectionModel(ListSelectionModel selectionModel) {
- timeline.setSelectionModel(selectionModel);
- }
-
- public void setSnap(long snap) {
- topCursor.requestValue(snap);
- }
-
- public long getSnap() {
- // TODO: If there are enough snaps, we may not have the required precision
- // Consider BigDecimal in cursor? Eww.
- return (long) topCursor.getValue();
- }
-
- public void setMaxSnapAtLeast(long maxSnapAtLeast) {
- timeline.setMaxAtLeast(maxSnapAtLeast);
- }
-
- public long getMaxSnapAtLeast() {
- return (long) timeline.getMaxAtLeast();
- }
-
- public ThreadRow findRow(TraceThread thread) {
- RowObjectTableModel model = timeline.getTableModel();
- List data = model.getModelData();
- for (ThreadRow row : data) {
- if (row.getThread() == thread) {
- return row;
- }
- }
- return null;
- }
-
- public Rectangle getCellBounds(TraceThread thread) {
- ThreadRow row = findRow(thread);
- if (row == null) {
- return null;
- }
- return timeline.getCellBounds(row);
- }
-
- public void addTimelineListener(TimelineListener listener) {
- timeline.addTimelineListener(listener);
- }
-}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadTableColumns.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadTableColumns.java
index 4c4c7d08d5..ba42286dec 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadTableColumns.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadTableColumns.java
@@ -18,31 +18,37 @@ package ghidra.app.plugin.core.debug.gui.thread;
import java.util.function.BiConsumer;
import java.util.function.Function;
+import com.google.common.collect.Range;
+
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
public enum ThreadTableColumns implements EnumeratedTableColumn {
- NAME("Name", String.class, ThreadRow::getName, ThreadRow::setName),
- CREATED("Created", Long.class, ThreadRow::getCreationSnap),
- DESTROYED("Destroyed", String.class, ThreadRow::getDestructionSnap),
- STATE("State", ThreadState.class, ThreadRow::getState),
- COMMENT("Comment", String.class, ThreadRow::getComment, ThreadRow::setComment);
+ NAME("Name", String.class, ThreadRow::getName, ThreadRow::setName, true),
+ CREATED("Created", Long.class, ThreadRow::getCreationSnap, true),
+ DESTROYED("Destroyed", String.class, ThreadRow::getDestructionSnap, true),
+ STATE("State", ThreadState.class, ThreadRow::getState, true),
+ COMMENT("Comment", String.class, ThreadRow::getComment, ThreadRow::setComment, true),
+ PLOT("Plot", Range.class, ThreadRow::getLifespan, false);
private final String header;
private final Function getter;
private final BiConsumer setter;
+ private final boolean sortable;
private final Class> cls;
- ThreadTableColumns(String header, Class cls, Function getter) {
- this(header, cls, getter, null);
+ ThreadTableColumns(String header, Class