From b9f00dd14c85180019b1c6868ff7fc7d2fc80854 Mon Sep 17 00:00:00 2001 From: James <91348155+FrogAi@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:00:00 -0700 Subject: [PATCH] FrogPilot panel --- .../toggle_icons/icon_advanced_device.png | Bin 0 -> 9304 bytes .../icon_advanced_lateral_tune.png | Bin 0 -> 25011 bytes .../icon_advanced_longitudinal_tune.png | Bin 0 -> 16766 bytes .../toggle_icons/icon_always_on_lateral.png | Bin 0 -> 39090 bytes .../assets/toggle_icons/icon_calendar.png | Bin 0 -> 18009 bytes .../assets/toggle_icons/icon_conditional.png | Bin 0 -> 9965 bytes frogpilot/assets/toggle_icons/icon_device.png | Bin 0 -> 10538 bytes .../assets/toggle_icons/icon_display.png | Bin 0 -> 18009 bytes frogpilot/assets/toggle_icons/icon_frog.png | Bin 0 -> 44342 bytes .../assets/toggle_icons/icon_green_light.png | Bin 0 -> 6780 bytes frogpilot/assets/toggle_icons/icon_lane.png | Bin 0 -> 18874 bytes .../assets/toggle_icons/icon_lateral_tune.png | Bin 0 -> 28434 bytes frogpilot/assets/toggle_icons/icon_light.png | Bin 0 -> 14116 bytes .../toggle_icons/icon_longitudinal_tune.png | Bin 0 -> 18593 bytes frogpilot/assets/toggle_icons/icon_map.png | Bin 0 -> 20481 bytes .../assets/toggle_icons/icon_message.png | Bin 0 -> 7068 bytes frogpilot/assets/toggle_icons/icon_mute.png | Bin 0 -> 9233 bytes .../assets/toggle_icons/icon_navigate.png | Bin 0 -> 15739 bytes .../assets/toggle_icons/icon_personality.png | Bin 0 -> 18906 bytes .../toggle_icons/icon_quality_of_life.png | Bin 0 -> 19593 bytes .../assets/toggle_icons/icon_rainbow.png | Bin 0 -> 47107 bytes frogpilot/assets/toggle_icons/icon_random.png | Bin 0 -> 17966 bytes .../toggle_icons/icon_random_themes.png | Bin 0 -> 16731 bytes frogpilot/assets/toggle_icons/icon_road.png | Bin 0 -> 21042 bytes frogpilot/assets/toggle_icons/icon_sound.png | Bin 0 -> 12172 bytes .../assets/toggle_icons/icon_speed_limit.png | Bin 0 -> 14360 bytes .../assets/toggle_icons/icon_speed_map.png | Bin 0 -> 25586 bytes .../assets/toggle_icons/icon_steering.png | Bin 0 -> 20946 bytes frogpilot/assets/toggle_icons/icon_system.png | Bin 0 -> 20439 bytes frogpilot/assets/toggle_icons/icon_tuning.png | Bin 0 -> 6495 bytes .../assets/toggle_icons/icon_vehicle.png | Bin 0 -> 20196 bytes frogpilot/ui/frogpilot_ui.cc | 1 + frogpilot/ui/frogpilot_ui.h | 2 + frogpilot/ui/qt/offroad/data_settings.cc | 12 + frogpilot/ui/qt/offroad/data_settings.h | 21 + frogpilot/ui/qt/offroad/device_settings.cc | 220 ++++ frogpilot/ui/qt/offroad/device_settings.h | 34 + frogpilot/ui/qt/offroad/frogpilot_settings.cc | 425 +++++++ frogpilot/ui/qt/offroad/frogpilot_settings.h | 103 ++ frogpilot/ui/qt/offroad/lateral_settings.cc | 431 +++++++ frogpilot/ui/qt/offroad/lateral_settings.h | 44 + .../ui/qt/offroad/longitudinal_settings.cc | 1085 +++++++++++++++++ .../ui/qt/offroad/longitudinal_settings.h | 70 ++ frogpilot/ui/qt/offroad/maps_settings.cc | 312 +++++ frogpilot/ui/qt/offroad/maps_settings.h | 51 + frogpilot/ui/qt/offroad/model_settings.cc | 611 ++++++++++ frogpilot/ui/qt/offroad/model_settings.h | 54 + .../ui/qt/offroad/navigation_settings.cc | 330 +++++ frogpilot/ui/qt/offroad/navigation_settings.h | 47 + frogpilot/ui/qt/offroad/sounds_settings.cc | 164 +++ frogpilot/ui/qt/offroad/sounds_settings.h | 34 + frogpilot/ui/qt/offroad/theme_settings.cc | 869 +++++++++++++ frogpilot/ui/qt/offroad/theme_settings.h | 67 + frogpilot/ui/qt/offroad/utilities.cc | 5 + frogpilot/ui/qt/offroad/utilities.h | 17 + frogpilot/ui/qt/offroad/vehicle_settings.cc | 281 +++++ frogpilot/ui/qt/offroad/vehicle_settings.h | 38 + frogpilot/ui/qt/offroad/visual_settings.cc | 328 +++++ frogpilot/ui/qt/offroad/visual_settings.h | 37 + frogpilot/ui/qt/offroad/wheel_settings.cc | 84 ++ frogpilot/ui/qt/offroad/wheel_settings.h | 24 + frogpilot/ui/qt/widgets/frogpilot_controls.cc | 20 + frogpilot/ui/qt/widgets/frogpilot_controls.h | 619 ++++++++++ .../ui/qt/widgets/navigation_functions.cc | 62 + .../ui/qt/widgets/navigation_functions.h | 214 ++++ selfdrive/ui/SConscript | 12 +- selfdrive/ui/qt/network/wifi_manager.h | 2 +- selfdrive/ui/qt/offroad/developer_panel.cc | 295 ++++- selfdrive/ui/qt/offroad/developer_panel.h | 41 +- selfdrive/ui/qt/offroad/settings.cc | 118 +- selfdrive/ui/qt/offroad/settings.h | 14 + selfdrive/ui/qt/widgets/controls.cc | 7 +- selfdrive/ui/qt/widgets/controls.h | 7 + selfdrive/ui/qt/widgets/input.cc | 27 +- selfdrive/ui/qt/widgets/input.h | 11 +- selfdrive/ui/ui.cc | 4 + selfdrive/ui/update_translations.py | 3 +- 77 files changed, 7232 insertions(+), 25 deletions(-) create mode 100644 frogpilot/assets/toggle_icons/icon_advanced_device.png create mode 100644 frogpilot/assets/toggle_icons/icon_advanced_lateral_tune.png create mode 100644 frogpilot/assets/toggle_icons/icon_advanced_longitudinal_tune.png create mode 100644 frogpilot/assets/toggle_icons/icon_always_on_lateral.png create mode 100644 frogpilot/assets/toggle_icons/icon_calendar.png create mode 100644 frogpilot/assets/toggle_icons/icon_conditional.png create mode 100644 frogpilot/assets/toggle_icons/icon_device.png create mode 100644 frogpilot/assets/toggle_icons/icon_display.png create mode 100644 frogpilot/assets/toggle_icons/icon_frog.png create mode 100644 frogpilot/assets/toggle_icons/icon_green_light.png create mode 100644 frogpilot/assets/toggle_icons/icon_lane.png create mode 100644 frogpilot/assets/toggle_icons/icon_lateral_tune.png create mode 100644 frogpilot/assets/toggle_icons/icon_light.png create mode 100644 frogpilot/assets/toggle_icons/icon_longitudinal_tune.png create mode 100644 frogpilot/assets/toggle_icons/icon_map.png create mode 100644 frogpilot/assets/toggle_icons/icon_message.png create mode 100644 frogpilot/assets/toggle_icons/icon_mute.png create mode 100644 frogpilot/assets/toggle_icons/icon_navigate.png create mode 100644 frogpilot/assets/toggle_icons/icon_personality.png create mode 100644 frogpilot/assets/toggle_icons/icon_quality_of_life.png create mode 100644 frogpilot/assets/toggle_icons/icon_rainbow.png create mode 100644 frogpilot/assets/toggle_icons/icon_random.png create mode 100644 frogpilot/assets/toggle_icons/icon_random_themes.png create mode 100644 frogpilot/assets/toggle_icons/icon_road.png create mode 100644 frogpilot/assets/toggle_icons/icon_sound.png create mode 100644 frogpilot/assets/toggle_icons/icon_speed_limit.png create mode 100644 frogpilot/assets/toggle_icons/icon_speed_map.png create mode 100644 frogpilot/assets/toggle_icons/icon_steering.png create mode 100644 frogpilot/assets/toggle_icons/icon_system.png create mode 100644 frogpilot/assets/toggle_icons/icon_tuning.png create mode 100644 frogpilot/assets/toggle_icons/icon_vehicle.png create mode 100644 frogpilot/ui/qt/offroad/data_settings.cc create mode 100644 frogpilot/ui/qt/offroad/data_settings.h create mode 100644 frogpilot/ui/qt/offroad/device_settings.cc create mode 100644 frogpilot/ui/qt/offroad/device_settings.h create mode 100644 frogpilot/ui/qt/offroad/frogpilot_settings.cc create mode 100644 frogpilot/ui/qt/offroad/frogpilot_settings.h create mode 100644 frogpilot/ui/qt/offroad/lateral_settings.cc create mode 100644 frogpilot/ui/qt/offroad/lateral_settings.h create mode 100644 frogpilot/ui/qt/offroad/longitudinal_settings.cc create mode 100644 frogpilot/ui/qt/offroad/longitudinal_settings.h create mode 100644 frogpilot/ui/qt/offroad/maps_settings.cc create mode 100644 frogpilot/ui/qt/offroad/maps_settings.h create mode 100644 frogpilot/ui/qt/offroad/model_settings.cc create mode 100644 frogpilot/ui/qt/offroad/model_settings.h create mode 100644 frogpilot/ui/qt/offroad/navigation_settings.cc create mode 100644 frogpilot/ui/qt/offroad/navigation_settings.h create mode 100644 frogpilot/ui/qt/offroad/sounds_settings.cc create mode 100644 frogpilot/ui/qt/offroad/sounds_settings.h create mode 100644 frogpilot/ui/qt/offroad/theme_settings.cc create mode 100644 frogpilot/ui/qt/offroad/theme_settings.h create mode 100644 frogpilot/ui/qt/offroad/utilities.cc create mode 100644 frogpilot/ui/qt/offroad/utilities.h create mode 100644 frogpilot/ui/qt/offroad/vehicle_settings.cc create mode 100644 frogpilot/ui/qt/offroad/vehicle_settings.h create mode 100644 frogpilot/ui/qt/offroad/visual_settings.cc create mode 100644 frogpilot/ui/qt/offroad/visual_settings.h create mode 100644 frogpilot/ui/qt/offroad/wheel_settings.cc create mode 100644 frogpilot/ui/qt/offroad/wheel_settings.h create mode 100644 frogpilot/ui/qt/widgets/navigation_functions.cc create mode 100644 frogpilot/ui/qt/widgets/navigation_functions.h diff --git a/frogpilot/assets/toggle_icons/icon_advanced_device.png b/frogpilot/assets/toggle_icons/icon_advanced_device.png new file mode 100644 index 0000000000000000000000000000000000000000..f849ec1324c71605c3f84e11f35d6c5262dca4c6 GIT binary patch literal 9304 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 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_advanced_lateral_tune.png b/frogpilot/assets/toggle_icons/icon_advanced_lateral_tune.png new file mode 100644 index 0000000000000000000000000000000000000000..17ada9b7e17295c38ffed21a61f8d5408d5292dc GIT binary patch literal 25011 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≪@>;NRN!&Q)@LvdS&e1mEv;@<#&Zf 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 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_advanced_longitudinal_tune.png b/frogpilot/assets/toggle_icons/icon_advanced_longitudinal_tune.png new file mode 100644 index 0000000000000000000000000000000000000000..93aa435d7fb795237b89bfa7c096840df15cb537 GIT binary patch literal 16766 zcmZu(Wl$VJx5eGvJ-EBOySqCCcXta;a1HM6?(Q0bLvVs?fB|S)u{x0==MP<%Rns! zCyk&QKb#u<(;s+O_r-p?=1du>;$)hk*sYyQjU|ylp`=olxEB$$?>k=7_TJFzFIX9=} z6^DLy2-+x1{V=)+=n|&X*HfAQ?ntAl6Ow`roQsBOQua_2_Vb`GhDFO!x1YT~l+dZr z4+dM&ZM9o7#n4Y65)g#E=77k(0mfjuYH=c3v#Ft>;brgkaH_(_rUDv?h#!ZDh$uE~ zS#lnhi6u(0yHvLNK#u=qNlEB}2%DIgN^clJYxNWy3gNFfR_V+WTB!Bi#=>4_4{Y1f zl9EISVQ2-meC;od&1>e($h40TR-ISO@!cwblT7lN=)V8ffwG*Tq~}PRH%;5kDm* zWX(7=E=NTH&r5 zPTv&8-k*(EvyRh#-46w0nn>X1=vduWgBf%j5ihwf%t1Picz&3hKQ5g54;HggH$zZT zQqtz`17f0Hf?Yg1Ra2C>q}x8d#H1t@UtefwUo6Ofe*SMv`t45N!j~X3hDkcF)0(^D z>%4q@U#}}ahksz`6b!hkftQ@F{upq|Se*C6^dTLM9BT`Uw=v?tO{+aONJ^Hd+HM+# zYAObrwR-1$NzIj?RFsroC@6~SojM_aVNTyp^H^yqr6Q=41qB5S?mwOWj<%e7iisPR zDGw%#L9Gxx5DNX&*4mJ%jrYRO={cLjlg?<+dCOnA(FY9n<7-vQdf(kF-$zqR%ORum zW{0a47VRU1$Z^jw3GW9ST`#CXr`xHGrGvw$kBhNze@K(noT0^@8!FH;#@?368rKVv zP$|FwS%-NB5tYD zsy%Yl%0r8bF8VU^mz12GoKX3FC~k|%b}N{d$x4E!??3e%R|+Jh5k%D!Lu4^dR_k_- z9oVco8_;~ceSEHV`aWRb%i!yI-Osu)ad6rXP8!UlkO>9-yjChTs!sWtfah!P!bXI! z!c*bj&d1N&N;F(tT*OT^2!s0PgEJx#5i;JUgiKf}W+xX|l$ssW=su$9fNtq$VE$jC8%e$lelg~Hx1y^cG* z0``;&S?XYk9hqF7wOzv`tPn{Km^8v;k+;j8@L=Gf#u&wFvKVY}xPm=UodA#1)T>5}5&M+XOfVy9oA zNNJ@-g2C!R+2P@lk=2Aj>h1shwC@5QwFEr_LIJ~;wl=FtLhEEhSy{LXXPR{}CKebK zarS>yNh>c;S3-%aw+3Av7u}v$d9#Fjk4qwDjIM}hz=B=8VoQX9_$ z^OSrkiCh6P=sY#8$*9}wrjcGg{W6=&aXZWcqnMKb9+n3oTaj@KUIe(iXp1dTU=L?2 zRrZKl7$*O!b($_%rxeBl5UW->>(Ej4behCed>g`wEN`iFp@AUKGoC ztkxOz6;zqqE*8ryog7Zt%w&k7b5c+_208VL5s|V)@k`LM)>_Q@e7rqn-zd_^D9Xyl z$Hb6d!~=b>eR+QNzL7@CA$Ee3d-wtcszG5S9(>l@+bbyH8A}XojzO4$B>irK*;!e5 zdKy+cP{24Dm7{7b)aPtr8eV(ejAOe0_Kil7BIfcnocBzvrDzUPnWp@M{}N1dKiA^l^P9JvpEG~ zI2nUgqEHOr%XLN{gwWiEEsKkbs=;cVk%q@ctaug6OQSe`f109bqDZal8W!baA)1o{ zX*yTnv6&3}rO{t5yRH_A?6V44RKE(8r@<1`rBzf^WM6G{l~ol*iWqTk@36MdWgk1M zj2yCRNlS!UXbcoyFyS!`&-r{2F|QQ{K|bvg;Cz`Pm3q3m@qDh2=WV%uex27*4~^I7 zIXQJKbmSbS59th5V0VAG?0(e?UZtSPe>n{l@XD%!tG3J}D%mNNh|%QFo~_Kt&?r;- zAy{TRp^+p~nJi35Ox(vy!;@LHp_M9JS*(YS-Ns8vNtu-wbI&_K-D0~!hDcahb#ixm z>p?5(4s|;@o&kd`jF*31FmIa0o8#cEW5_Y+pNZh*JspQKnT^f_oDX&>X zRYzuK__v0e>!i}sA_77Jf}u2e*o~yj=hQ^a^RwNP6X7{RK96di6*2l6&fWL-*Q-K8 z*XmCGfPWcN{MGmC5P#O1$`$}9gImq}{Q3+@AmDS40Y4UM10K3Om~+zutiJ4A?CjME z(P=h9T)M;q77wvBJv|*;qu1u31IdLx z`|H=QLb2D!i*q}BdzmJ3UFO6Id`^n4t}fsH?s*&)d(2&UVty~>MjDt0`a{xMNRQLS z68s=u7QDhd38Ed9o{U-pEewf+fPlvOb}U5bdf7(kMa<_(^Te#7y;dMR>>UGF+HUv0 zn{1Pmq?(Ty|5>9KsIAVo5^xWVOvE4M<7hZXiqXU=7_emp^sL`ghzkg9?*|jfQ~{CT zhVrS;t7|~GHnBG3`}(um+n~$sl!yomE1q9p$QQr?Q)vt_Atak<-7gP_uDDEF0Gssu_xHw(dMrjWs4D-CSmFClgXgQ|n^2 zpM2_HxyasC!}jpFu6sm*Tf zd%()0CA!&eN@@%R=4DmU7M#@|xEAeBH=4M=whA?2-Ix<3FYHV^ z%{FSA1PH{UkX~Bat!1X1_dM6P?$uBXBT@TQia8*P6;$5fOGRE37zVE3-JS0==4tG_ z?K)erDMaLQT&574ebK^l+7N41c%?3RM0ZBo?OOu;M?5?{3p51mXD9;H3|mVYKP0K; z71C=Z#d`exy7PUuF)=YEq#l*=Fy(OFyMe>U6U)hhq_XX2?>yn<@YZBexBFAj7 z&Uh*$%6z2-f6$F5Yn$!Nf7P3WCe`w<>n1|3cBqxbSKbeoi+xLoNk}L%#4Q>o4+})e zBb&!Kr>O!Th{7kp$L~UGRG<(Hh!Qc0VQz$&4Hdh>mbc%?Oki}G?RHqic3)0!mYK?2Cwulv2h$aTd~PciJP{&NF@XYjiekjwj@6g^oRXXt)0P z13;wm$1~YNC4E9a?Iud(z?(J{ot-m75&~%z);58`hGf#m2}@`_2xKsK1y+z>?&09) z91wX-04OP4Cw@L!UQTgSnp1}vMc-((e?^6>i>*ixPEAeq-hoBW82c;l;o{O8@b)rS zu9xZW%__}-;NPQaIioA3OW1%X85YSDmk?8AlPJn+hK_DV8+vrxjVwZn4$h1}ixMBa z2y>Dk^xmEjPZHZ~?sl6m3Fki{Its*ufq+sLc@amVQTZ^In?cube$)|OI9 zS=klH!Sga6m&MKD3XZ?EQ{=_O)P|TiMpXu|u0vL!lcsqUmy|k~lxS!|9Tvn15ChNA z<6;WUF%2Uz{a9QR0U|W_%#$+>_QyGZ#3Km^2q<5lUlvJ04sTK1;d42BAL$p%O?-GW zaYQ*k<_pB6Zcp$#y_`>y#>XAamzpkZCg5xm84Ww{1S9r4=@XWSZ&E;K`(kJi-{7GZb z(`&W95m8lLjJP|V3p{o?U1Wb|7bQr3DyeVwkkBunf|NJQTmSv%5B~gB3{!?!-zZVB zr3kw~4}1-dIeKL}sNexqa$M}W&Pbw({YW%sRm95+Qc)^oH9P&U=u|o_OD}H=0LI1r z>`R?`l9(GgS@?A|ig@&qwwc4c=K=t22CGPIsfsD0$r4Z?gVZnhRw>)_51K)*6$p8M zRfgpt%7Ik+tvp`Sx-mJKja}DLt5&}t`8|4aVq#)#e{c36vBb7zAOYj+oR_)AZoy8)ZsHZklwU-?{!`RvY&4-fByao9t!~x!#hHOuANv z|9-eX0}{Xd6Uj^HTTT63u5B-TqqqR)@`yilSIbpTj0fo#E)y|@_l{B-q5+$=_8Ulq zd~PS3J$h}mKisa~Pw#D(+Q}_dBxjMsUc-yAj`AP!bEcpaJ>K6P?d;C8kS;db#0WK1 z+Y_gyv*gql!h7LU%217fPOqB?y){Q^{nF$nQZ#5E#f892;_^tuDPn?9FgTQo8|pU zL*EdMB^|yvCX`Cm0g!nK>{?j=tkzMZ{@#v2alq&I_Zqe_0N(kT&PFj`7sDXhWq&m8 ztAOC!J==4)s2^%X5-d7ptfam@Ce>~E0iU2216{EMv@?ExAgZjl{M*^}dp*>J30%(j zP%#zkFQfa54d@;LS8>`0;|^(keS)_t^@S7-Bn^xbn^hniH$~j>E+dUI_C-UNBmAqM zB7GTIYOq7VT~BQLO7cC}*seN~0*CviDnjbV9jPO}n5n2J8Nz~Xo7VXao)rqrCNkK+ zx6@{r@Z)v)EOT@m9Gv_?r&kQgJ#%75N274jXi(Urqp!jTPtirwQ>5*|0BdDR1}ZNt zFCT=ObZy&5ZLW1>bn1Uik}NVn;+dplq9mHj>z+Z(w|GX1?7I906MuaRN&9M_(@LGOYaoN21Ba?^Yw~VKXZF~V1Ve#jyG{K?~Q8` z)(D@t56cGAkBayQ)#)%eh+7w)nE(Y zOQtfA80)H9pfLSRMgiVU_4SGF=T>o#iLi;{1EC_;tNNrYSmu!}6n(`djVmXjN1u$u zJUA7JqMD$-`fd2r<<_eOfcZ9rEvd48mw*3ypTSbJO7-OnQA67fxW$0efl4d1#LoKq zdd~8Lbk#1y^vq0MKMjc?TK+jXgW8Q3ji*f1kwxi;)yYzs5)+YO(7u=t2&iKk0DR(^ zIo=kxwsIwL8&G#)d%7RKefx8G2k(rsjYWL?M^qnOi&@&fr^EMhu|%%1tX_!p8L}B86~DW?`)J>c>5m9vcm}{?Ky65?)j+8WTt%BE z3FKO`^+YUKAp!{VoxKuwqmsR}Vk!DwYMSTX@LCZ^A8fvnb+c{hg1FF)`dBw4gru^f$QI_kOxkaGWwlh7p(MW9sw3sM1R1_(%gJ;l#7I2&?jHM=hhrWU^- zTM2MZh#+|vAz$x-#QN&^+&Co=ioxKSm&=pOyv%yhON8+$QHgvwd5>AFooblFpFs;s z!~z}-9bGLwEP`=BVCi*e0dz>-+-6ruMMXufVvHIwfGCuo)fC`*n%jb3gO}zVz5g<= zMxE~e`6J+drmmMVex3=cwPw`o_uLtpbC@ZK-0h}XKr0`}{1h01t+37zZeZZs5`#H~b!>=jW$MlL?u3oj#=6KxI**QyU4^B4(K}S~w!o3@Pl+%9e zM=Fmmb>M5cHjJB@dCH*MY)uKD+$V(_Ig$~E^^lp7fsi_<74cUM&5sS(8h5XAjU;fp zJ6uRJp4^sz{7!Iv(6l+OrA48w)}SlcNQ*onWCns+AQl6zi&l-(UY5t}n#2*JqJ@G! zmM81@=qNzJB28>Q60(QwoVa*hi#p&lgc5cX2PuQW=rvN%1N;^C_nQ;g<*8RV6O>BI zQk1?}L}aFxKXJRF{mgJeE3nQM?uB_g!B)f~0X>lLSP*BYrwf{z(T$>@EZ>U8UB2DN z#v+WfPtzb&2_p``8ME|39ZH_9w|qrU0GJ4ZLzfn|6R*pbm+PSfYdx05)93#Bv)x@` zc6DV~bRy2=BR303{+O2LuvB$}UsX-wvxqPuF(Ap!UpHF8*G$xDa_El+1_p|^kMk}{ zWO*u!RUTO?RU;;CNbI&xcIt?8EvBYJPiGd11bp1A$mGC3f_F#t&*Ue@VKl1mu$vMv zgsu8?reFT-?TNra)yj*_(Uu9*8f0nF{UL-mxoS6dbi4|S=mm40CB|!f?dClfSzQSzT%JC?ypa zeQ05!1{a!k5s1=ggEuVS74YWs+~?t&lai8xV7hv~Ri))9WMm|iMMDpZXuwL~YS{mE zWMo8z$d;~2UICmgM`S*zj83yU>agOUb!0|OO%2;3&DK6b?z>5YM4T6ok(urd%Osfn zP1Om!%4kKVgTrP~=#l%CU>T6nU(s2vFu?E;wlFFCPf(y%#2XFT9#Uaf+Gtm9^3Mg`K zm8a`Ny(@X8)738NB6sz!$Fl@jiW|nVLj5k4P6K%YzDNsx-?ao8$o`?kN2Ui06(FiM zck4lEy2!mkSvxpbYrBan!0JC9&F1oXCPjn^>NrgZFj!>@__o4>NSvAJyU&_+4sA+! zAKsX;ihiFiYT#U{(zV#bMM0p~O5)7xTpnj zSyHg6kSK3s<#l~Wj7>&3XlR=ya_LB9hZuQbY($)~Hqu3Y%_`8ktx6|`t6(X3(E!TjR%(1N)|+&^Z49=zdwicj%N9`K4^y-2?4TBkzFe3Jw3Vf={sZD% zlDj@GDjbXb24u|`ui>CfGNq0g<1D6TQk~OSJfV@d?@f!{r-1sm(#C@EgQmMb&R4wY3Rnk305XBH4^A|mDzOQq2bzN^p zHL8(26#g3+Re|+{(mXLcn+zyUqOBk-I)%(6BqS7oZW%gh9u9PJvC`N=Gf?xVY-j=b zOLAIs6Rokmy}~+WiLRKYAubL>Yc(H+B9!JC79o!P41?#sVMS!Gxi}q8*m}LgRwu=x z?~(wWpXd9(4j>}YXtV(n=8>;Sr6?mis(kZ7M&?_O&x2~~(>}45mewV%0VhthaZYyB z?Jy3ab&J2ZCAD;rnLrRP(ywD{Vo3%pLkP_x#av#;7Q3}Z$kIB4g1jFjfs*x760e)@Tq$CR&TMT;s|y8{{FSJRg8Qkk{y9+QR{X=$ywkD6*>n2(nm zZBKyYR{(yXeM0sNNP}y$aGJQVRYEix8mfFA&t-0-W??Rv8P4CgXD;+A`}~B*lGp>^ z{kUF!1*3mei+kD$?8BA(O$u;}YS;|A82^RKBUYEex#I?afU; z3N(#TI7og?fjUHl!W|&ODU#f)lI3eZRCs1Fn}8`mr`QjkXngpQ>aEEAlS@g5a`0RR}+R3X@bcko9sDmw=4=3%1QG7>c z&l}9Lz;3_iTg>hbKASZxdhJ~$Vr6Han!fX$z8{*z^DY6F2B*!U^W1-zPnXI9-Y(jN zj151s-$9~~kR$h)qIqd7B{_m!`@=*;MDoT<&jrY24xeSsP^Kk5jxR3-eHp@#-Lr)N zdHQsGZfloGZQJOi0&9V%iM9T_9&Z(_MN(?F53KU#(I>BTbul9p5cRH*E=Yn0dDv+C z^?vQ~VuP#dHnR9|3&ayS0pOrLO4$6SUR5j@`&4nlA{?->(|JS&&PQA)b;Tshfz@@M z$h}1LcB{+tF8BQ6v`bS@%?a8*NT|gCjegT|az4SI)OeXw+txP#ZT|9kwVnSrOXVEoxJ)l&cNFY76N!5eL17 z`uqD)9EpjU3ec+QdibfNq%Qq%hu&2EehDO|znXwckU0do_);`F;ZT8xYbm zRvH?m{I)fx8KWhR*@xE)cdqKmxp*j3lqXLUar7HKXl z5CTebuDpVRRKRAbg5I`}lSt@Mn4@UwT2NNYg8VI<8Hy@&Rz?QS@lKa#Oca@>zh6`p zbe-Olg}nUqq>Wk`t7oCgdpEFVxt}e6d*_KR+!P)>wdFPhTVLQd9fnz~b}G{ye`PA2 zahNQnJi@{{y2rKvAR9pt|6~fRw5m;SqU7jYQPJzCZk}F(WgJy!ovis0@CLC=Z=uMotszCtrl3rxuX~h~VW|KuJij;DO(CKA z;SG?Bg}g54nB<|6%{toK+gTEt(}ih=addja~nYw zo%c$GN-38&P9>Djpb)AqsI~lSI^z~O8ChDgv3&jSYn=Ko_omVNC5+91=CA}2HWrcd zm_S0~_oelRzmnlL5jlC$YzYwwcEJM7gU;i#)sF{t545O+y1KdE1fgwv#7NU-of4hq zmGrc?xjcc5DFCyjA^rKYU8A>ZDBv6LP7^Ch2uOFw2M6r9x#?7kUrg?wiR@Lq1EIE0 z`x15&r3O}m)Ie-u^Z9X$f9CMGmL|{Y>F6oM#zp%2_{{PJ{NpCHeXR3)c?Bd1R4lAk zr%zeWn_wgo7av(nNs={=b$=-txN{Jm2;eCRnDIZjokbyWf1d(Sy|*Bl7YrOMqw7Ab z@ieyVZ_C$xWGTr@iVd*cNpnhy3iXc+$; zgI2xmlY)dA8(~>pF3{(_Fbr3v6_oL2k!C2@>Jc-JQvO-Y2tYdK@r(RCT$_%tv6Mer zy6^Gq@=D|>;o;%1iIal;fmn?CI7GzQ*a^+@eS#DeNH7-DrICg=z1Re~cJcRxdFqqq zdN_!YVY+pvqJ$i$U+)3J5&2@q1k(qA4RV09s2|`E>y_Ap3fu^BLx%CBZH1@J`0|GY z!`&+B0~y5=9&?ZXJ+rHl#1uxR`h)p=JYlfFB?cN87<6pdO{~W(BydX8m~qm!I{}P` z-YL@|N%JD;B*cD1TsEhDkVhnhW(mSLgZLQAr+SLlWUGNtwSFHMg42-k;Xf#Aw7{&u zDy^X1{<CO<;jWadfs0es$Kh(s#ZwE>VT!ok*4PJ0?!*h9^TI%LV@oDx@oo59EN#$+nSo1`!UEWYH9kQ344E` zH*8nh!9D=aI9&|CB-&q`iHpl~hd{an4w(ijj8O*-50GWM2?>NN{l*hm{NdSHj+k7; zc_i{AwN<@`c5N)JwVMgGQx__!>yX}-!)}&m0d*WN4zp6B2-;lqQeBHw>F-}!b<>+w2zlL3%8Lm^Ke972+8r2< zm3*i{8NToUYQ0r%PR@C>uOC=UI92%$63&<)7Mmg*q}{J}XYvI{6awRQJ1&ujEnNt>Q`TXZ@Vd{Yg?{V!h!=ULh9j8XzIt#BXOk%ETz}y(7WF!BM@!)*<0< zZK4IM*#_n(=wK==Gn?%f0Akq&00aPvD!5TXV5Y|N{8thO38O>(ubqC_@W0Rye+PE^ z2>iCxgHC^^#um9z;?`Z7Ei?g2t1r`aIE?Pa)F85x9OhP7 zBni+nh@{Po5H0U7&p&Ijxg55;L!O_(@6TiK*sZUTZtwo>Y;SM7dW-T^Xcpi-idkCR zg4-0f#=L#NOj6$<#m6K0Qpt}yY_u{kxT2bwPO1ry!PV$@u(qm=#pj;vf`Q$F<@UQ= zygYpU#I`eXMTgg`{QUgwV}qZ9(@6FA_ivi_+4E_fl`|wKTiG}29->96fSckXJ7xJ} zGL>%nqXlJguW|?TlCzftO&68v&}t@6ASUg5TQFUC;M41{e}7kE^w0Xx~!4 z#gp&6+B?h3Zc%{CW3xv<*=OG7!Q4)Ksd=YriUg`2Iq@ucgH;y`0E&V{JN$QZ#^$V= z#5^tsQAGf@PYq^q?Bff3=bN^91)a!w9TIebn#%>{UmC;27@l8_`<4RwjwChMf*^f* zu?>uasd$S&I?}h)g(ei;cDcvLN2GbC^x?D#3O?TR_kGF8$xrQQ8%PEXphyB^8wVpf0W2YZDf z^qHZJhAl$H6fpajNGb*uth8-T`5p8#>j7*@F;%BgsHJ0~qDWKu7!EdBQAodY;^N{k z4~R-4PiQcdorCbOu&~UeGO_|De9U!i&#-V3xxmWmQ(%~D9&A})EX54G7#eh2r}N^k zK#2+=0@iwcKEWCC7(YhOzNiP9AUi1w%fQUc%(t}JsWLKqb!~0!c7FY+rJDjkWGG56 zYo4i3IA`HvD?-!}^(fKu!y&i+@d(xqBo*$$P!@CEjff7~*5wr$lX!<1&+UKza>w!y zAFP+d{fzW*L|KcArk(mbaL-Zhh070K)k9*z)Sy0-J&+29A(aN0*9-x(qNH<*r#I#j z+v+E15~-1q^15C8Hkck3&Jc`cBhi4OcV@=p|8n1km9cXJm!HfR8ym}ed);`w49pxB zjz&A(HQ!^vnq>2ETdq;ioL^%tW$QM#xfH@`= zASVxf`n$JF ztN-FSs^Dhuk*&0}vO3mrsQq`>eu5j})K06$O(HYmRrrNr@IWBWBRs^jF-Xgzhw!^i zx>lsfebqoi<;VFN+rp?=6H5-eYIQdly&&KgnE?cAB47dE{Ui?2ieR`K=-4Tf`FpyL zd|&^1b#!)gELIve+=uA_R)h%|S>LB0dzu3(d5sVsK_Gqld|m!HUa3T0TKexs83B@s zs5RWRPS2-hKddKsUZ5b=EkG(I#KpyLh~)XiSk{-wBpPOu(+%i$&zPeO-P;Rwr{01@ zhGCR9+^>xxZE0a~$QTzD zCBDx3>@hHqTWi?mu?4W?ij3+K=o;<#mpy=!Fu7Czv0#2#q09S@(zT$}t~0tba&h>( zr0e#bpP%1m@Di4#q{i}-Ez9_yy1v@8njUY9t-()j~%2?>=1 z#yRtFz#XyHT%_=3Ni&CvMMMO(!lE#i!ZOj9LA3axL(Qa4F#>4z7|m=1Y!s`uGF)-V$hB&DPn6~UG&iF~}@7sIq{cK~3#4OsYykn8B2+;PM| z5V}tc7zXue&V}wKfD_&)L>rYbQ%9sDSo&B-RWzHZ`3RXQtRY zf8j%QH>IGXHT$RPu0{psJJyx>3g+c49X~lW^%@bm`lX#0q3{WhpvpPJ6qZVRyvWFY zy_v@Kosa;dnVFA|4;lAB3Moj|w-J9H{<+-!k7hBN!df#;iCc8zn6d*(9V4kH<$t+= z_{Ne;z=O0iPbvnufex;E_yu|dBskqvUTR&B18!%6!Cy)HyN0|f8uo($c6b30k^E-? zpSl>!Z%ENgat6F9OYs$ISCSI&<(cF-1^JARB2gD9egdNYSIVG8!aAhTlOdMl`3zGQ zpTx*4)Dzk7#Q9kEeC9xw@o9TaXlf>Kbg;BE`6R^E zquK4r-RsuMRV*>2I!(x4*CG}3_}41DoGFeMB#ggHHTDq*=Gf6lfIR;Q>KFdlcQUs= z$UUMy_@wptRtknh*qX@J#8Vf<$19yimY?jWpL#+=La~Kq_Qc_m81&joQJaFEIXbSB zTDkm=4lia5BoHB`F`Bdb=+srEKtCS?6BE*8R1ho0B<4nSa5a`lpY1#p%#**dN3F^+ z0c`-=_epj(9~>OS)$0{o42qPrgDFZaWYllJ9x8)Iehjk^O3Ez9v4(*cnxH&!(h3$y&7D-uk-G`8 z`8|eST}?S|)=!_D;CH{kRsS|G!!Z3s@NFYSAKHxt&L zhbGntrS}^{P=*>`Gw-s%ND?Z*WZ&zX>cY7xMS}rynhGo?jmw<^9<1(d*d-Q9I5h>Buvz(QRpm z$NL7GAm$i9;6Z{`Wx#n0(~Bp0sUs@>-bu>iEe1&WKKDbW>9%npm90i2FE`-N($cz!W2=CWy`g41nH5jajf0gvdh4$VD5hR^Bh^PSTA zUy2_Jbs&nM+Ot7`>HOXHWy^EZg7K1w=x#I?mkn-3d6Xg?@I6Ojg@=WaFf1*3_hoQ$ zkbGMEl)4{|vK_-K>s^!RuQP6Df|xf!yfu7;02B9%Nput2ntNjSmA{|Ae?V8@#}kk+ zFS^}o1WYdC$nULN0bW-fycaS{VD1wztgXdEk+V3V+8lRs2A0OSlgBF4pjso0X` zK!KD6(Z=mrRUIBXuU~sXqS50EaCO-Qmt8Xf8PRS#1M@4J^kM*58~3Ly95KNRu$2A# z7f)UjLS#%RikrvK@lvElKkE)g~5=@(@-)HU86MJ1O+paRuLnu!(^Gtf*D z4dJEN1k=G`SPCAULeypFC6jSuw^?hnfY;Td%r<(=Q}0B1^B5njTpK;EI0K{ZK zlD?yvgK@djCzSg%RkD=r1PcpW1-R8F6EGw!1cT^0O+GM$1G*xS zd4eR-J-%Zrr&p*MJZ5BFLe91LfMWLe#wM{Uq(0cWjX&;@OCgEib41P()<^88@;hGY zdrmj62w)~_uy(^+X4k7(TR$4X|QhFSGZ{G7RDtG;9plCZ59e4 z?>^{0gOrP$x;kyC2t{oAB$c=afPSUgu{#8ZtiP8;^ZxxTu)t=tf>np6WYiS(Z} zJWgw4=_RrQ)CUT#Ex^tTM}w;k8~A)Xm91>KXk7uzhxG?l7m|)HR9WzHjHwwVq&?(J ztKTI03vD0FDedkH1q}_2x0EHN8v4?w8DxJ~8F?+CL07AtpS;K}YwXV{=+sRnpsy07 z@VBGfPQ&uj!Vf77 zCHp5s+CNfK+OaCXn~fz9gBHziX7gk%SL-BiG>HgSxZ#RFhJW%8^`9ZUk$LU48F0hN z=kl)L+O9I&_R%Z8=W+77Uo4jRDk>@xIc=(7aOZu%yC@9icXyjKSvZ%PNON_Dj2ks3@xM??ACq0HLySE1; zZ4P1BtHl7`%-Dqek4zNI#I&p|sEFH;-h#=mHRU~xA{I~sDDSOyl6$=}rlwP$r6FI2 zE$VPY+G9=p7_p)Ekb=CVadXGakd}Mzf@>~g8ND$AMK+v6qG}Pl`Q?K#s0q!YyyjE&R2-uMuEIGU@(z|Xz z@2};Urwq7oGu^w;#_;gVyCZL5?mXyFv+&D&KENy1!D(V*F!D|)SNBVT$%u#$$eMp) zAd%=;ME91I9RA1B(VrOrgt3K0%nx(24|3sDT!;vP1{$nnk$&#!yt2_yX#G8dn3H$6 zp$No{aR-tjOdpYCe5{1wZEk%$o$2>Zp<8j(SFE14# zBF4O5D%2tBtGcjiU6;-7{d>MEw6!ewSLp{7dgLelW*tZ8#!B*1tNy2$yWEIaKUqui zi#!8S_2T^8t_vtd=K(>*(`>8!}&E3*vpF#PsyG)qw$%#>g(dCn26FP4nPL zAKuSktb*l~=LBH|xjq{-8;p>%UcVl!F#;D_F--ym1tkwfYKA3=ot9XOTUxtV8GdbO9bHR*C7mX2eUtpFZ3A4sia%Xwm9c6Xg-ClOT#`D`*mOSh{XVC_- z-DzglJ9uu^+ga)Fc@Fjs@B&(!n0p77mV5Wx5I6q1k(95@%}~QRg^}-AV*5Ye-yy@G zY|)SSFg01u{D=KnYQMU~Ca)OA4)u1x;dZ@*Ri!;`+KU_;#ACeNDYOQ*M5cZmQi%W$I$hj4fvQ zz;Fmc;Q(1lAq8ByRIpLNbNKt6c#k}0%6gD`DCRx=DGM4T1~v1@ns+oOJ_VvQ$FF=q zkf$6S9kK`HG&?GVMpri{r{ccZtn-5d@h4#6Xic`WqeP82R*a8NK9#jeTEP-{(ZuO5u`OPI<}d2>-!2>4WLbRe;Tf=)C>sJ1G=o{Nr+EX5r%GR!|VGB~jxWQ6+p-Sx`<_NEEX ziPj0yfF@pQWFIf`w;60i|M1B0aCPjrZ=gOF2#a-TGr+mjLV%8O+bYRVyAq@tX%ggV zEppPLr&51*Y_2efy=ug1935OsU1`nFjj5joF;(nP1;r_-F28ZasnH(&R;6R)S@sM# z@GR)J;XIl<4K4a=p(mWV%mxTuxSX`KHsDw8xVo8Xik$}e8c7AU5T7O8l1QD821(1A z1Qe~yIiA|^N`6+ipNGq=)Mz`yAh?5ofP#PiUchJ3dc%EMUa8qQJ^LGMh^NtqL-J?E#Wng~ld!_CTaQ5Qg zHjDx)k)Lfx0(PwY&yJJdj#L{oLcu5_+oxH2mqv-DTS}z6kw!YDL+S2j=}rO3FCn4Q2-4jh(%s$pUf!ACAIt8{ zow;?+d7e)lB2<)Qu+ZP2!@7gK@rjl;sJeLTO(XUUqe{q>G<5C)M3-%9`ehyH`eo{<%WN{ zT&KF{A_SjNjYc`0eQiTf7JRk_iQzg0i`n^eB1dy1(SA&A_*AM!%kQ!~=Et_1&i%w8l zR$NvBZvHm`)hC+wwln2-zRf?py$YqWovNC{I1fKji(C^+PTNd3F! zG&#LNXwMqt(fE|rIq-57@$sap4mZGnU@JlKX;1MTBXDpAF&CwfeqB1f7zr~W-3B>4 zKi(yMOnYar42$P?-T#D8woCf#+M&@w+wncbIixy-D`avlaFB~8oA!W||B-?SZTC=X zY{OnQF0S%ycV6XG{XGo}_32@&j~qBKH%=zv+SYvx$O>5b1_a#tk&6MkW*Jynm+_cDmzvn~h%0gEz`H~Td z`Jd=jH~BLr`aZ_WC!J?l=E!yOnWnPn{Qi5|fx%};Swrn3uPuHX0r%ISDT(7C!}Aj9 z3KZ=fx(!^LG8j1D_cf&j-?4*dA_$G)a3;5HIW=6T_d>n>irA-1H$q%#hbt`v_9i6S z2K7d#tpO7U`S73|xM^D32wYlu?yn0q{kdYnD1H~iJS%o9e|>GOtaiolcTBbI{liVZ z7q2jP*>-*F`g!URTs`rw^V2Gqe-5AHde_<5*jVr%x1)tKq0bC?BzeFbFZ$)s0v7Rm zq*sklOJmlm{Njxuwf$L2n9;1Lf`K9vFLN(|+wR0me7a2I$GEKEy8r&|C=@g^6_PFB ze(VIS$fwEw6d&kB;0JA@OUkbmJK#I$Lx4Nh0G?CA%Vo6&&YH3R5`&OM8;0wX{sDQh zo)=@4Z)sY>x1{O-x;%~y9C{3ZODcuik&B|r$@!H^CMBA^K+VC*R4`83BAL!%+T&90 z_5el7UVE=BT@-`bc-TrVHu0b0H!l`=asOtcRzF|(Ow0V=#wzzK|0!f}5}@m*H1)$b zC=8D9u-U5o38Urjn(x$n5?YLAlS>bs;26H4OHqDsquWLxEF@B^UBi2t+_eUmyBt zRIHS}Pz|~z@Qae1y>W%auTN$1Ia?IiwP4?T#BuI>;ebEBz`*u#%m?*WHNKWE4ALZT z)!-h=SlYOs6NtL2M&VF~2IWrn9#Wv-z=f4a>kjpm$6N+lhXety2cc1|)@HhlHvfiy ze>vg&xyU@FLcjZ8JPEXZ-Qt^}e{t-{yma;38iGVEX)53o983PJFQ!jbGKRwP~sQ`mfFx zs5$!)-1L5uNiRfV$>&ny$BT^@xuyyUwG8=Q+2-=t`$ z&g%R&>qnyh7y(O5_cIrYOcV>`ulLOY7JU9Tjs0@-ozwPEhMseG@)Ln-u#@;TuoCNM z^7#YLaRhyY#&80Zp>Og1kF<<@xsi<{Rqj%E$9`y4E%z-mN-^s6ePzin7tP8)0udt9 z(sl9~)JR1U4u?Z5aG`zQPDd!(Pj2YZpBeFYHyoNJw9~GSms`AvHs(u94*vfczwC|| z$S50^2_XhN_dkt_Jf3!+0xV{fbV< ziq3JXF2>NpO1WW+S7`iF%jVPfi(D#M(sD+`4|a%k}E*3>=*-b|;EHYVjUhFSrY7C+3QWq7$T^g!7$9wWB(X&IX4;#q!g&mHj%% zSL6aZyZ(@a|5SVYT9L?-<1=mxc=mrh>*bs6NEiYaq1@&ayo{*yX?UECAR_&tQ_c}A zrCj4fFvmr_Sg;`P*E}gt2S-^dAmv*~Sle*|WoXnL7jEAfC^q`z6?*l@qjkmN|MVmqPib-6oER(v?(b9df?Uvl`AegmbH{gTy7Q4X{jzAUIg zZhvz4+txW*WA4G%il~0gdFF;AVN?PfHqSEW>LLqhAwx)feY#j>5Z$8PhWih43G`H@ zQ=pF$?JfOrXOpb|dh3F{M{>IN>7&OEoA%Rml~L>8UHJ?VDytDhAq?sG=pULDy8T|5 zh59V(h>n6_5W<-^dG6xlJ4Dw+TN$)$`^?%UM30vhw<#m~S*Z!RR z{f2C;5$Z%oc)Dcw=jD@73?G`A&p3n z3DsvlY~W%VTUOEo`pXO>m5CQMgUp7?x@N4E?NCXIuL&8zLd1_o4sWHxXHbEP3@;J7 z&JuppGW0B>GQ1zqN6mU6MM`y1f4aYVJGSDB6?}8Cy8g=|sW;~BjkP`k-kyg(E%X>u z7Jxj?97x z_~=E|Ie-$tW-{I%!o6!<_`CN^xL6d)rxKiplqZ-+1&?l2M4AVTb9X-zO-BGDFQ3|;R|i`chu`cH@*+oM~LSd2ob*v)M;c!Mpdc|TV~H} z{L48wY8Va-Z_PNZ-gSv(WX7cze7Yo?-l)PR<9F%mNT0ITKG=cEi8={yUTqMoyI`nlC3l-sPp1zBYO+&qr|M4Tl43z@?`}(c1(&^2s=-;u8CLsbuzktk{4dOo`iN3yRv?BoKWJtmCKwH60arV?%31*X zj6&#H6-h3cVcb+D6`k1U30TKv=>7VW^BhMB5`DlwbR(HMFt0CujED!ihM6WcZCOUlD5&QAUdw=JZ@aI~)c}9iT~L{FVi3RYkfo|8cOB!tJp|h-gzZQyyE66@YC_C8Jp;lx35) zA|^Y7Ng7zF5M}xoUV&34^MpRmaIK+IXKn+Y1%>s;A(ef zCQtZ+cE!>F7D4*2rNB~OkKmLs8h(zs|Cpjb-2W3sWRlH0w}*g#%Uj`NTF8!5=5GMt zZdpTN{(|QE%-p*LK;`IZ{m_sQ7jzn2YjK7&gpN8{fyl z!PrGrphp46)qfO%9;e^|AxTU+%8rc_1pmDstlED$t+1EyzYt8nXpA@%Iu;c)t{H#xw_o#r-kRrmlJ<(-= z-KI>B3H}t&(C^=V_@X^3-oQ^_Jy@Lqb;Rnjb*1!LK~}wZ$zWhk-aai#GaQJ+bNHBx zN(5PKaNIzFYlUo46|}7!%C~h)LsT5}{o6-ghX9mnb&*4QgD$qdYL;2_l~PHWmd*Bc zutV%aW23hi>gv>2hjWP2R;T`(c!z5iS-6s>T|O-DO?={mA{t8p)TZpTMqi8|=k^Mz zqk8dZGPbic&IAtBaNpB*R0)pEX4>@J*cHlbq^O$f#igVty&D9vAkSPmVj9vE9@Qv! zkisT!>%4<(Ja%wUYqR-aDytcnJA(;L-dsh00=3Kt@B#XJ0#(BEX~Ppq!z<8K)h2PS zqHo|X;1+-U8m8f>O5+Oq`8vobG|P?>xGdkL0!rETE-E`umX4vIs)L7VG{NW<%FO`u z7BV}j^TB$0sJu`(k*~8~kkDvzt9(3etgOcS_wO%rGoDX<+%2HMPvch>V>^;@|HG~Y zZi=niO>aWxT(30sYK zkD_2`nmrfij8e`ejs%?))hJ89UtT0Y>Ib;#J%JkJRwx5=7Q-j z_f0L+^n?n2F974!6mdV!I+#JOy{=I!oJbSB^J&pR07A2G+Mg;tNnFz%N$rE zTOqLJ5`w0ATH%xb)*C`9$=?S%I#2htKCIiv7Q*s;$+|`e_4FWC$eE? ztBB#Y8iUJMt;(51wEBi%qC)VM)nNc9IV^Oug#yja{LP0ia{=AylF!s#;k`ncf$yy+ zx;`JjO=Z=q(?8$rSC^GR`aF?qxhcBIIkOJRvj>!oRfcTJc!6UxE8INhPbDe|7ktHT zqPYvQ^jL!Aaq8D!i=^HL83*|iPA>MUWgSvOYzbV)x8HjknjP2rm+3b;NoiH+)=pG3 z8*aC&i^M%zSytEFojRn$E7aW?d2Xc);1<_*OWMY9`W_5ra2206$!xVe$Xu$uHIQUL zHZB#^56!KRNWS5lYBg-=P+4sl9W8dCvCqd4BfpD;j+Uyo=95nlWSvuebK9RPjgpGQ zXM7QAuZYb`-~|yyCN`#crjrYC`x6#SOG00^|uGhxo z!p+_{$7F1{Lv@QI-~kQTSA*;tzaH&T4s`y))Q43uS05jL{1})0; ziGh37Omkkam6EZs&pk9daD%>KfBd#A!H)dJ_eAlhzU)c+qd-8R?42INj@Keb(C*jY zAHFU$Z%hR*Q@{2%iSvAunF5ctjh=(5)@28Po0%@k%Y6n2<&w>&u~u=u-ctA;WE8H8 zo&%&t+-fABp)>sEJ^=Z&WplkdYG=4cbO+E2lZm?PQz7}FQ=LDOGcMr=>@Kk=F#TAH zQtzmM$%X)l76#fS#-Og}SEbV<#P~$R3pGHWsQQM{JLgEa0l)`E4sWo<+9yUv`@V?( zVf-NnOp}NCh4bb0sQ#aTgy%XD(;R`rmT!t;BEI&0SV~s(rxVVR3)HSKNw%dd0PU8_ z{0WsxfdN#Vf*(!^I}+vF?0OL&mr8J-;G9LgO$jz1Eb_99QUR+XW_&p^vd z#is|mF`y%*u^AngeR?q%zzZ=EQM;G(M!>n8AITQrVHX<>*+JzGr3`mFm@1w0w$p?B zET=J2G-lmu!J3-sZ0;KlAPrL99R9{bv98L$o3AxObR&xOgueA?wwZbEacVG>lDzf_ zfGbJL!sF7D0VavfH-IcV9gEp&AxQ(!gCDc>kHA!O_y@|5t8zV@`xUG%1IzXP8FS+} zQ2pN;Ug>0_u^Q!;oS!6`-Zy}{)ilLH!vbLvfV?=f*l+uz>G#iolEFw^mlyWsaZDsJ zmx+Au&6IT7!4zG*K;0Uvag#@ld*xnPN1W--OMmQ1e~o%JQK}psF?2_lw1bii?q;s>AQ1T}@ zX8&bb&?8=tcF(w_LH$x;qV1X*29sv_t^DeLqugs2pB9m_PcGZq9L#)9&;h zK7oEf&$q@Mo6QH29)W&Nbe1$GnB&iU+Ta~}z9w1m6;X67waQ7I@lzesG*Bh^d-8)^ z_TBriD~N0!IeqZfsZ9L}lzVUKUS8gz!4I?w0 zwdfS9E9%b+HP(2LH2oEp=&tJUtHb$f9R7ZHCG%g;7RVN1{98Jc;99SZ1`!j*$y#HL zKI*w?-%lpVkV4Ok?Gvi-`zJ?1@+tPA7W>IN^ZboGNw@}WuAoY4yzYTtDWlSw6@(e* z>F%?yz_ZIbNZqr474+*funG9yI z_wJ;E>QaE!_knL%AUg}a)k+ejZdhIIOQ`(Af5zj>4Go4-gsck-lhjRdxIqDd{fqb) zp-2o32ku(j@$?{*UOI2hOp>BM4G}WpxFu7wi5kP_fg|8rR)^_F4@)wmWk}-X!E6=V z2n`>5!F$xe`=_*R|BNE??t#T(`J2>0}!k2;(9@4Rxw_=4!>yQZ{CB9dLQoH z5^F0xWBTj70Z}2+CUNNf0ZAEwb4Li;_m-#Ye_v(e;?E_Pu!Cqqukjd8++j!iZ>x#X zrISe3$e{y|^f(MB>ad1x44|{Bw3T`$S%pH(sWpF?-*VPCvM-RfQ`rdta@OAa`RFZ@ zBXl~pvHKPAA$#WZ!mLrs?0SH=sd7F@zYzofP+RS@-JLYO#X8S>{wehm1<6_W7oYbV zuXy%C*j~6XpX_oTRzA8c0kVbL_O;+BfvgSmp)U3<%;k#X?Td94D{;`l zK}%c!QU##=w;+GJzW;4;O*1#SbWDxJt`SmOga9KOEhmVDd|S9|%JoiNs%bzFf= z-2MsjR0;&Jq=?b!9SPz-$x;M^CmdNr6OtY%Bee=;6V9VRc1278${;U(@G+YLwIZF& z^t7@F5hKYBG?@pl)1|?F>?GyDs6BWRf6$=ZHRUTdt36oYE5P-}=BH-$14zThB(*l1 zR;LSr?uC@yIKV|gudY`eXpj7(f)@&f*LzavVsPstbjZK1|7)cq=CX)pXyl&X(RE^O z`pEXZ>+~CdguY0by&>>BaOMX%KAK?N9S_)tlZ6NNB?f?nc|228)hpX-fbGU)Ef7+K z_WdKT)*4W@%9Rpn9mSTi5FpEknjyi{Z@a8J7d@?Ugb> zqKP?7yRMu8L|)4Ez2eVEwgTI(ZU|Xs1U*P1>UBEXA`Y>E!SDE#+7WnBtJCwZq&gxA zm^Je`D`V`o7R{CH5vYgF*J^fb-H}3OM?RCceC=`|bZ(1sI6GWny?rH#Fh#j~W1vBZeoDWt zSqjYN(XkfwsVFsu+k~f|XRdTD-LD>P|C@~|LplV_OvS91N_jj@lJZgPD$<^Yx%7cd~X(Q`dnQ+l=%RXV(xj3il=dTVRb zCMG`vsM<=)&7MhUn!}viB$k%?DcmPZBM?ei!Fp+MMUQ-)yRG&fgS>Bc`~eO^R`*#9 z`?NDm%NC#j0`VS5wS%Y!MzL1$U3q_n_6VnnoM(aaHBv@AkCH0==0t5byfgBnc$`uP zSgrO?W6A~@O~zXGe?M2yZX5shGB%*)7q!~_VQvbaTy#5GZoz#-)~3@y-Afj)>%nA(2%8V+7B~^>WPT{rv)_0Soo3C=!8$x zstE)pm_D>BHi)VCdMk))19XGQw_zmbhOLFC0re9P{eM%B4!9CMX(qASwMwaN0Lrjk zIu6?!aFs`9)@3e6Ada(HTN*{6+7zY#YD}<$Ts&t^xCSP#wDtBSp#YB$E=*S3z(WdO zeVmHW;e#r)H#MFo4%p(?2ed48JD=Y?B5w^|eHHcg{Pz@7zwC8jz^m@hGZ3ep<_DA= z+n+9GwbQQ6Pq6=&V6!*%HNXU!%A!PNB@h0dK1l0{XncQZ*3IcMJ{2*W)Gi1Bs=a6W z>do2Xjbu$e#dpN0t=_i9D&dI5-EIb#Sa{g5;;M(vCJJ=Ez>%8HUUY(l-oTAeZ0~vw zfUR!V?+;p-gW#-4;w7G*sjy7E5hv|LkK1XA#jsr#1wuuNcn}+sD$sbg7$hap6Z94I z{K6ZS0SAB9$~MI4KM90eVFE~H*gtPHllfiImHkm6va%PwM3evIQ0M+=OhrHf zq;!g~)1L-to0m)*_y&-|SN}{(>@a25z83(zwYBp(TP^;ti3(Y_E4mP)zB^QDe~6zq zJ^h9hBPJZ{B7?|({UOYn<=U|bRdMN3b7{%S43^dbZKHfe0}E(^8Ewrqn}&66N-vqz zAZH(+p?%K*$e62oB(7;`Q;38EhGv&JfO;qYe$a~AXh}yS1uh|HeZp+m?D1?=q-8WNwm2&eEXnS~mZ zg0|~JjB5lpW^8({UYENg#G#(;EhzNe~`3eyR!l5Qmrf9=wfz+O1US#vyMD;Oi-DC3wqm$N`zZ(a& zvKV3v=j1VOj~(IsXMDg?4_!lNp_F%+Joe?5tF`ZL;&7j^Wsn$wS7i7I$9PzT%=`1- z9#FG<0WSU)jlIIUsps7B4f<(gWdWBGWdPjPQWy37DJ^+k+g>mj;?@!Y_h%%IlSwAs z$TLIm>;>~!8OkNjCTzYK_dqQJd)~!hYmO&y+>in-ZoEnT@bG!$iKN9;nKySyy<6YZ zA(C1;ns~>o4tZN}!HGT?fzE#_F2=j`M50f1uGRT>bEK(B?VzG(YbQj^m{0LZK9L=e ztWRh$oc7$hZ03I#DOmd5sTgJ~D^%w%yIeU%hpwfhg zhlSmL2~^dK#1#<-5-4X1>;K{KCj!22Dhp*_0_vbCe+21o+#r0S*V>fE2MTRiN^zak zUzTo0%9}Q1X)Oc0;)ERWNOowz!ZnXSCvWjR>&vp+eG!2j#Im1+KpmMi$~G;3jTh!! zM%UIJIAaiQOEfC)B=uoo&qYl=r4Rmb>qCm*tBdbKC{|K91AS|Hc5B~j^erQ!JQcjJ zmKSN9W`V_(5d0;1BG7J&akjX7xFmf5G~(e)-1bfKi37Nr4Pa{hLnpcmb(DFCHCSjq zLR5dT8mjYqu|N*;280tg(Zk>#vWl-@ZW$i2ZlH~+_9_pZBPIi)*LJAE-PgHb=d6yz zUlG#!I0`>wtA4+OK3@N`Z~dbg{U0VK=no(qsHIB@?N6Dp!+(^;!F)3jN>%vyhKpa$ zF~_?_-CMp>Pfx&y{^!aj(~EE%$In&47(+u`8c!uj$&gdP=H21>GWRoLXE}OYTz_8X z(_Qg*w2%85+mDvTj>cIL$Y1f)1Y${cPEhEb9T5nrP_YTU(gbD95Yi8 zdWq(KH`fcg-2o2tsHr37-O@KleC$c z-waV_G_}?bpp)CNI0ZIu@SN`5wmiIR5Y@SP;A|!nGdb|JD@N^ALwL(+V~NGI01jVp|s&g}6<^+1@Tmwx<) z4%EFoF5hK**-93BI8q<6+hUrh9DLYa_^qKRS&4DvWNDEQZg7Nf{ZJ zysG_)B8ysrG%@J~{@i}!?orZ@w5U{6^kiFR`ce$sypqywll(bG8+pdzWd8}p77rSR zUkLb=Z<|qeBHSh`mFeN_;)`C4IR{rU#$UpCQ$x6tB}(;1xEg#hMe$gDY298;j6pPh zhgDIXD$;)vss4oXPpEVYXG!I;DaXDhLqK#+Sns=Wq^8;s0Hm#MamHHQxC$RS$_IIe zs2f!B#BouXg`X}bM{|`>ish1|_2h?qPIL(Sx+3sUsl3v_5ozAcAx5BUz{WYea1^6J zYX81!*`j8kkL6{lmlg{lsf$)9y&e(@dBy8V?APVqp8labW!K#873zY0Y~t_Jt8_^k8LKAn50-WUU^1SkBI}~s!pD5y*7}ikQw#76@GpQR5U_{ z?i<^1v|<51;3sWUoy-$=me8;l4!qt$4NsJxlbC3;raoIxuD=;f=I4j}gC`_X z2R|jEEyj+4byIQo$APYB60XN`~KTBYcXUo^$Ih{V!?Dr(+aLB!~-G>-PmlPUjjcrCV*19*zp3aH>YgK61CxRiJn-HlZ( z*Sf5>Oa8n3C?*BM8-V7969bR!A>l_2>eOA8`hKQ*{)BHzU1kAT-OBIU4jH!vwQa-w zJh9*XjQwSAyy#~ls1Jo)A^auUEDSGsaCRvOB&^z#o&$cf^KjML3fj6z_;hTY>PA<$A!es_>W^74Lfbs{<9S5?{^GkDEs-R?sKpyphMbN(bRmzdGS; z#MbBWA9)10P5%4*!e(NqH~Jb$<(YYSIOO*LLYUPYsD6HcfGp$r_N=cnKjd@nEeVJ9 zGAe}+dyV3^4<|J=#{FK(6oUmZp}cfACge{*95?H;g8~j{^S0!iD|gn1${-A(XVdxb z4l2w~f{i4D3}a68`oJOa0h^C_gBsRCGKS&QDTIr*D|Z2fE(U$uciJvXDes)P2R(nRM@w zoAU-_&X>Fx*#4(1BTgQ=`$s`btjnbLg)ev;`FgDqGj)>M#LfptDiLK&l$9@dp8;9I zHkOo+%BZJ;!vE1R%2?@c1~fj5(ODekC1q^AFKss7q^9 z+q~-!ctj6Pd1iAT?EVm5qAja)xw!wnr85|Z!q1GS@BeN8BZRfHWSMR{IPNE@Clds> zZU&^jtQLV>Z(Rl&Km;&){+$4xVOK%AL+3A;*|61$V3+GAeM8^+A<}1IEFR<=GfHLg zln$jV2f`gGWDbZs31C4wym2-VJU{Mn#CUh}_I}#0+R+j3KT58!2>1KzqkpJp`5?~) zEkd0jiL+gPp9N1O>>NU3Z^(tW6NG51Bkx5U$TXv0;}K-NHxhi<4D;yQ}?a zTN{~ez;`gzO2&a3)q5q46U^F+`88h!@hl%8M2UQl_bEa`iT-X70s7yHk zzv>kxCz(mA3RfE_N)jS`I{nH*v|z_(d}a}h+5fNBcJ>jlPO^fmG^F&EMQsQhlvcgi zOeRGcUhMTB$e7s9tWHdDkQk5|8 zQZ=>otGu#)!w?^qd!;c7y5Q&9=d);^E1A^cCyLl-q$~eGMy2%8>?w9tOmkAw z`m1abth9+{YdLUXUj$lH5_kG#G+)H!7?7%Pz}+XTaDlvj zWRkxgA-O#ahI}LbFr#|B#P;{rgqQQ;Gc2cz!*z>@1Fshaybm}n(M)6ulw~cQTs1*@ zEfV71ms#j-vbv9Oo^QErE>X7jH2|5AQ}znob89V=c7L|YNL}x?^x*?ip}@$nDmZe; z1mo`sk0d;PO^Fh|u$Ji2xgbP9 zdj9kx*eGVrW8n*6i%z|&ag=Zxba18mFV9b?i8BoF?JUJyeGVUCdA6!bU^Wc=Y9{PS zGnicw``N%@>6cbe{D<&-TbYt?AY8fx+qs{ggNeOu!FJewC=`*FU-7e<42$GbbZY=_ zo+sZ#gtvV)N66bPi9k9_eet6tywVF5Xv}peougp1I!*(O6{Wm>e0?F5L#4v`(KM)~9~^cW2>`UOC1m#Hy^;uNzMmCWpOUesAnX`=yo_ zgOVeN9(N1kgq3`f-+o6_mC*g~#~zNcLT>j(Y1c3tl2T=)kY@s4kVi=&48Q90>0aQn zs)6PQ_y8u;#OAy+MH+T%=MV;YEi?FyU~o)cKE=R$j3QP`Utvn*)GQHm*|vqtZ*)dc z0XjF1aO`aQC77c{N5oR9NCteA>#7xv{x9Z12b-pFk4c;uW1b5WGYl*S{^PA-dqnU( zP@0xGn5g4>!)>0}cyUuM42%Sle|S_1ziXhWMM}zJwddtqHPZ-ZUsxjDz2jeW&TD$8 z`1QR^az!jEb*i_2fUQn$xrs9MdaP6|9k+@>Fvf0=Fz{tPq+AA+@7m6wi#=^=mhX~h zRxYMGvo19p*h5teB{gBqvZuCn&k2h@bF3quMZ%jwGT<7ax88A#v&-7Ritjo>78u4z zltpj^bFbbgAvwH5a(n|DkNMDKjS%y85QpXCc^%!j$tCJ>=Ja7aKmHuc^C!RSS43YCD&z!gX(y(Yt9k(V0Ly2r@fUU9F0f# zqQFU9zM=P{%FjdSh9a-ZJj^gz{5qR0m;1quchJd2_7nMh>%ryT~2BwhMe%7 z3sEexi-JU0C9bmkJ1Vue5Dah=RTnknYuDzZ8PGwQKc;c*5YX>PNp84ufdH_@_hqE` zJR^#j3cCA&FB5O9`@ZFV|Hm}=PvUq^wm^c*KN(Vxl!fF-Q2yh-o-Lh8Sb7t@=uG%~ zQY^+x4|V;p>e0Mk)cL3jI%Hvmg-@2kSE6OSATqR|ymyHjzMQVXmfF8Fn1i4jkxZo} z%3X&fW50IQ=kDmRO*G&eq&3J{g5^ly%h(;!8P0S|ba*7Q_E>;IXZHY5tycWiAJOxL zbhFKx{N`-pfpzT~!mseRFOl{myD^Jl^Y>gS6&*U-53ujZ=sFAlb6`BR)(=C}xC_hm zDTZO6{l=Gx=Ut6byYxqo6h=up`{s~nt72QcEvUkZ13DIyyqMH0asC*9QQR2oe3IGq zG5g9#DaIN*gh6n0aFajZ6nASi{Bm{}f$c8+?ELCIM8dU>*RC}7$9 zA_(=Yn)w^B8mj{q(lte=Rw7AZd;9I?&_4=3+PUh*Gl{A_NjYhH)xA{-<^ zWC!3(GbU9$AK=QA(>#Lx?4W~2QuE2=TiTeAflgjQXsgx-LKE0TyHGv*@s&emir@|B zz?ZNCkHJ#06aw6g4FZA`VieJOGInE8WrexNEY*rFjcRY`{V(1MzLp>*F8j-MZ<_1QOc$~Dqed2vOuW%{rBMh zA%zFqTz@Xsu-Y}|ZbbL{U^^$IN+E1Wvh})N2d-ZO5&L~ATyO2j1C9E+`@Zp)1 zz00Bps2Z+2_Xi;^(}`a(mVC;YKO`k`gzuacMhk3hlPfJ+>Wwz4oFu_BoUZJjl&{^r z$#7U^mETnp%1i%TKG22h3C^0iI+7;5M8R{G`kL_-PLfCa5)T?w0*mhyR-MgTJphv8 zNsihUZWCcRQUvS)E(?kIaZK7ohYNs}>1Gm}4NVBL6fX7CG432<9DtF&sPQH)1|if- z)a>rpfZj0s7D`eq$dbwQGMk%F2@A5(Y%U-cH-%ohMen%2w`pq+Vx+)a_w0hGh{sWS zJq_tN=S`voaD&FjWWtjVLB{@~|0e6fW;&aVNVnuyF`%20lv}vKKdV-QV($-Zu_k6? z0m9XxTo6Cip(t#LHuhNNpm{pqcMD>#PlGw zpA|xdXeHIX#CMUrpOZ3>RpNhFm;>Mx38aD0#`CY2p=v4?^A{;a43ZBRqc3qDHT2?) zL|IfJ5be1TFLm6D?YE;MFFxIM5f{mn z(I&_9*&#<=P>UjC+V>55EBvhxXFs)g82oOllRYiCJ^F~xvG8=g=h(EN{Y}>x2`s6T z_yn~YgkW=JnL4R+Aa&2?Fm|__gY1O`u#UH11;C~1^*C6KB)^( zAsYH1b%m^=-BZ@zfWWcWx5(YAt%fGqFCOfLJY&Bkn?{}$#8(3P++XQ7MYp(5N~+n* z)C0}8rFg61ivt=)rbluN(y&!*Z<_Q%U{>a*g8d{y|2%LzQ#W*=tqdM|dr|JyHcNZ) z>^7njI0;i_&i!P|EtkmxpFBqs`Cz+?a%VN${{rp_$_{=zL<|tYaFqo_1h@e&xH8_~ z(YZc<)tfyWAL_6bA8Fyt1n}KIPz@9IFP8OE3#*oWDoylQ`9Y;7-x}WsmVTg)-+0R@ z<9F>*P2N{WjKo~!%|#{L6S~kd%f+a;VX9i?upHo_sw@9%;s{c90v~`I2x8-E)#MYz z2Av0$j+K$yIv_rA;3HfRA>BePPEXe8I(J6Dix5c!#wB8K{6WvP5B6p;uSA4MI)48M zom7SO4*(mt{4sxHwlnddR=&gb&>1asozO3)f{GIcC%sRc!1>*b<<1ahE}x&ats=o~ zN(vGsufRZ(TrY1IDkKWn9E7Ig`jhgN&j)zAA-Y`hk*Mf55+C>uqy1W4MGG1h%gEj> z?{L{3m|*wsCi?G3Ikz<7WQ8kfr9H5N>;bN+-DgCkU#)<1AIF@T`P|u+#lBR|`8*bV z?j=jm^PHt*(Sz_(ijxXc0iak)c!cni?~G7^elCJf{>-*iJ)IC&Yq4tO;k$Zg8p&tmJy-Bd$A4 zW}8xr4Zl7`sP3#7&7J`%ATP)QOYu8U-RVfeX((?XTtBzno)*x+4D+23&&WCr>)0eQmQyyHNrBac-2WP)zS&K`Mqt;L_3xgi!Ad3Fic$~TqcM{uFlnc zB5>q+1hz?(5K6AktR4oHp(Aovwy7E~4hC1OzlIfJ4|SGrvjtb^d8twJUwoMY{h-sRiib zCUvc_0xe0fNdJ8v;`|f!@qUwfz87nT>b}sa09g=7DKqN@J9WArk`cgxQq?i;!Gv-&8Zz@UW+2YLwpKG zX^xFyiRTi`1a~+2PH{0r@x)|JxHx;bZyTi}DihQnzxP9|KeEHi+??ZE1DeS*wi;_v znn7C_$VCy4MFb(obt-pXqm$L&u&eNil^8BkMN9KTHS39vskFrjj_CWR!|G8w3D;K9 z&AT^0#D8n96>>OHeF9u&^>y;LrUY9G zrjZs@WPSo9+R_}Oo3a8+y!`!VY*XU=C4zYF9B})WkG`N)&%bz?{=?KxPa6|_@l7_S z{Pux4n$k$J=2B0g#NZyOp_cLzzfVhF{vDNnIP~1tVz*VUU-Z7&(wV@FGGfQ?GX6S) z2~TxX2|}24EaZ2H-<1T1t=?fEpUc2mbH@JCfD8W2o%g~!H)!5f#hdSSLvO*A45lq8?Ah^#0ass;@7v_hkNz9c9?~L*pqxib*kU?8 zhJ_Bes{g0T?j*ti{lM1w`TuA-%cv;ZwhIqEbR$R&-7$oKbV+xo(xr5FJ9JBTN{1jI z-QC>+((zEz_}zSKy}wy7in&4 zD^7V^maUt4eu-gYgDKUkrmr89R718;P(&?%Yv`IfbKIbs3Zo~^$yCnxAd7q^#d1!* z?$NhyfIgDAxXc1Ufg7<^I8}L!Y{|qE@X}QUda<6)5k9js{Wb<{LHbV;Q&(O-OJrCN(>WOveO$v3)Mgvw)i$~W$#bml{P`RD=olDF-2%~|1~ZXl>RK(wxT|^ z9=P^dWJ=FvKXtNDusd9DqAr#zMCsFP42d|1cdy|;%bW?_6f#o8J;UR&4RR^Cdz(O8 zcH=*kl?7RCJ)qrD-K3GJj$G+j9`c9np-IM3GqL*na*j`N;$58y$X?AvSXJt^KBw(7 z4HdE5gw5SV=G#bsNYg5x%!?Vwh@sjM9-OXevj7{ugMUyX2{irAyY3xR+9K3CaeJW3 z<)z zCf`!t1V*=s<=Z=KWY@K1yZx@36WndIwV6}X+$4IyLD;>uL%K4@== zfd6SVlFal`Jf%s_QtL{>GX4EDx^e%iG0xTRO-;S@jthG9ACk#vw0a=Y&N5V-S_H#o zT->*Zr$R*ftzq{kCPRl-;_-CEXhcrWq^wUR56LlW2(#()PCbn<%9_eoAsrV8AHtos1LYg~j>mp8UFT%u{GtOM z#XQkn__R@KFFKA zjp^AlSP4m!rFWs2=zRi4%zosNX_V8WBN6rmh?E~}{(Wm5rF#z+|5V1_8n|%_?NdrD@D)qOgMPf>{FzFWpoWm~hWK<62&|F(1a`F3 zqI)%q8CUCd+LdAX(kpT&OkV2`as+#gUdRIW=>?7HmV$#b-YYX0asIV^m_Fh3Nk|@r zeH6PFEEyO+!euq7<11GMha!raHm~k4&?Rm?=i%@9ra%x<%YlJI`#e^Hg8Z+<>}`v9 zyCg;q{dIb`({#J*O{+ggAnm6B zQKUP@Q)bDwAkR7CdmqNdSd?Ub^r4@~P{ye!Vas(+p(V+@HMV}#F{xv{HQ-q%9GzB* zWXZlTo_9IYSL^D!8Ys;wG?BERhC%Bh+xxgvRivh#zPAP9)Hw@m&p4kU-DRDfnlu*Z z`5@ar5eKEmS(D@2tj1AsI;4icxr@4F(BqVEz4uZ*8(l!<6EvMUe}|%`^V4&QyY}Ed zODzxm%^EHI(1`L2$#P&i=7#9JoeyiiL=9`=L1_7cNdOg zvi-g}s`^05DG-2^a^2xHD0E0yQ3-et?89=!3t}iMN-nDaqjsZ=(9s_$-J#n&^kNGk zJ6s+3_O-%-NRzZk(UcPhXK_0H!UcHzi`xUsl(ZVrA4`@UU`TbR4B+NY`zoTNyoFm;PXaX2!JnK&=FffVT zhz0U9j(%*&;eh{Dnp`rpG5~7UFxi`!^wV5qLAJb@gIqRjuVlEa{09o;gIXWNf250V ztJ(HoIU;lxMVwv=)Dz|BLxFROt`d0tm&bNYYY#~|@!d)!ICcL-LXulzwYFKUawVxz zPUMpp8J^kKL*aHApmecGyaR~ZY{X^(1QnWxL~Qyz2nTJkETjBA#Q*4aca>Dz`n5ly z4C50)@@Cr*e-YIhw7`PW?lkGeP8Y@7+f73s`nYb;>X@H(aia{*al_NEB&DkP;3H&U zObQncQb!#4Q^R{y@+ni?o<4PkU@u&TUt0<-;5=ga+C7+;Ty#RdCGf0rYpkiXLB_qo z5!=*C!Z8!cMrI+BRz$Zv=r%7%=3}|d>X0R9h14U-8SY23T0V@dp&C=6U&1Q4V`_%o z)zwwFgV&VZpb-JPf@&?|Eim|Cg|fJQp{yH@(Zc|{&Yc9~wmGop9^EvTK-7VPT0M9L z7+7|bRdBco!c}(odinw@|12C=jh4bB=yoVntCG2OOhK`AuNy_Kwl%asBU+4d*_78A zM5e+Ecv1)#zDr>AeOE~orocII8PFz0{AEhJ z0Vk}H`R+?S@O6;Qyo{!mmr`D$0k@K%S<;qRMX=!} zrHd^pRbwMU3{o*4dCp%xfCf@&<)qKrj$KDyG0HG>e3dYd`HpoOkzPFyxUt8mezO;)<_{iCH+zeTbGD5w3|3DQf;J?&s%+Y#AWvOzJ zCGk0zo+@wS%ZmufZ$4rj^{|f*S!Ev7cffga7eTo4R7qJa#Ks?LZ*bP)oFQ$9iD*(42_LVQmK z`*~Hz8Ak3H4%p5QL<(RRTVR+G80+WccUG~}pjpH!qhH$eirBZ^us#*Bdvx2MP!~?D z*at#cC@#tdXxsqU{+!Vgcv6{bTAXZG74hi?5VNAaD#q6^`UuXN(&sAizF0l@>6`uF z^E{ZM>J(k7`zDto)Uw`UYtEhfH6(rPc_8}~rJQtXw;32VMY|f$Xyp&v4Izi1~7l@kdgjX-Y&n-&)IK{gGQb*GRSoAZ}6hY+TYz zuYhyuS9b~cFwI1EOKUYsJ3(dNfuBB1Fx#T0)rS5-8hYrp2&alYK<88@8>6`ZgV?S~ z#Y5_3*!Z{CRKXIImt?Dub)P37bUB!}jpS5re?hO$p2mU=uH-23#JPwjyXj6di(m}o z9LTqeU!QI*l=n6sJNXJ>9u;qHgwbrMAWqA~OYxKaq%-z8ZG1?Mb6iVq*+KZ~fK~co z>oOLxmlX)NypvJ(0LXV!Td!%9T7(p4c;EW3VI;2q(*m}eMTm`;jF_$B1W^C}ux`ed zPoNIFpj>`U1C8{hhH^dDdI^Ulp8=LLx1Z|fY$BB+yy_~jKFWdZYsJf&tWChATXKU) zw3>IIZ_U5W{Tj1ZTF-K%8a@=c!A+RR_Y6?n%)~XqP>T}5IAR?^C#*)dcp->V6Q4N+ ze@Gmpsd*=>{vW`}icMDD5wktyiWWl}d?$NL0gJRsE8GSwq%#x74y`xv3b|i-F?u7J z;7!Iae=V#6+!k^k6Br5`1*WSxybB?owqm?f>RM=C!4 zn$8Y{eLw&2ftRIVKlSckC}a+mg!v`6CnbkuuWqu|R9x;USsJANc@TxTY+u;1-pj(l zf%=WALWCtQJGC^8k2Qyy#C3GWD}b%1XD_vqB@ar9!)qYx31rS2HJUm_Bts&Ujr9v* zQYml1lHjF(?LLXoWB>jU%L~r^hmv!ex1#y&NWvsXCZ8>cF*S?lvEfju%ZSqVziyo7 zBZ^;yN?Dek-`9=Sy{FaViDpO7XBfhndaCVTk{Jh^L{bb9z`R+Rq5~2aIUB&X~n}mps%Evx56cSwPL$&lQewOFDV^ z&@pxbl-ltj0R0o0Ab{VxppUN*jW7k-`kSA|I?6D#@JFMz)B-2vQYULVHgh(0)0>GT%Xc_a~Ggkk(1{gaaTwx~1@w;7K2-APQZu z3;+Qz6EG~ytfk{lM#P)4YZE7l0BIVD$K5cfTGC5RB25*A7 z9l|gAbKdw^}(M>{6G8JTHtx_BF6LFv!>FS|?I|%KAhg`^#RYv=$wgIx|xDPz!|T2gV#X&o*6LIM_Um%`Ku@a=rn<`wa%1W%)cO$Is{+`%D%gNP z$2&c|dwwLL>im%TK`Ft7DgxO=@V&6bs~IE&gLnG;$mEmt3}9xPfPMWKN)yIkPtH`S z6b=`y1L(bHX7)O8t|U4V6;B5+ob26TVIKp1luv8(QCQ+CWO1Kr1{<<^@0``-?b0$O zzG4dD*tLb3K8CcLtV@0;w-Em$O;3F+8t!X=HtC}Cr4)w`KQP@*`VcO@f2ez$k(*$o z_~)JCE@z}k#|zTcu4>FQq9$WR$5v|DtAK#(!K^-Ib3R8GZzkL&g0SpHiEi&1)*rM* z-4B!Cf(}D7La#ReG!tZY{oZDbk7e`_!usg9^Sp zLlXP6N5V+ZLm2dH{G88yJ|#H+KuarLDI`Q@={o9{KdvLVeCC3>g#oJ=3sosjEzhyRJH+=VQuS-8&M0qlk z@7!msnOpqFPRS4`KLRDVEfS%IS)1aE&42kXmZaAnxW@}czy`)E&Pqa)o3I={@(JpV z7@69E3O8KG7AmVvAlPFRZZ_YuTmFwqjkgp0C%_3<_gS{2hM%~e@q|pW|NmCX6Zf)A z5$6scLc5UNrQA<8%PC81VO%aM$%$S@+5B_?fVBjUv@BZ=_$8D7cwE}q0sw*7fI3T} zF(uBLDl1w%1O|*Pu?VLpQf2}(ul%YT-|GAy2E3?1fJ^LI!NnPoVrD&-_!V6kQ?L$d zxiw;{O#C?BQ?Tn8i2&1-tgFFB>9uH$g0&ib@c0oNO>eK+jFA$lKR zk?SH%3%MDL1o_T?{Tsk^W8mI7Jd9s0X;z$C*KNS_;?)rSL|tKTlOSqwDSJ>?}A2hJa{cz>G< zOs)ImmQKf2=UFvZQ&wqfZRmI?`di!12Ux+-2bbS4COWHoE0r_v6Xk!B$=^1Eo66-e zVP+V`1Es*H{}LKt^V6O~9dgM3aW-hnBZpQokk=YHrxZp!k89QiA#;$&iDmfEocW#<~Y`4Juw&@8* zLp@Ptf>lhZ=bXhc2;64gN@z|-)-EJE4&LE}-yg`;=*}8d@0js>5*A$0_emPgGlmEI zAjur4yQZng{x_>28`XqxmVz-rCz_&?8LWiCS54WT_a99#?}W(LPgvU=*?%1nyA?go z-UO>K+2>WOz@Ml%9l9H$$USjtCg;Y|H0tt@p_JtertexrGb(UfJt;lQ9Pd@PBvne{XMCxeU&Lj=BWubv3uZ&Ognoj6?lKtWUA z^wxDtm@9w?J_|9c$5`O<+8S@N7bo-XvVrel42$r_oFrg+j%#^(YgCyLIP_liO8+VZ zCLrlEFvj!sst{0x%0qV-P!&|sT-HN9At&BB42bZ>9%JO?A3owuJ_Y--|0SBPx^W}W zkw7yQwve`M5Gnf)9hvd)V*m}9gs<-8-fN(r9q#&M_ZNk}XQ`t3&5H72(~mn~J@+QD z8}`xFKZ-yDf71Mrgds+mgb@pa5-fYN9nSD!sC~5~hqqM=h@gMy(DKDYDw5xy@i7d? z9~;Q$!!!M_0!QW9+WDZ9Cr}*$lzO;mtb%D6yovx;`@h{hbqL198k5Tg!E^;2n zbeM2~r&d#`-g)}&xfCie0b@6AYi@Gqhl{Ky+(AI3gZj9CnEPJTPdHW1rpDXW_UoEU zbh}GN)#;vZcZ?@NGK!EC--xSB#yEL7GL7z-3NSa9um;L$pJ>AhoR1VP)_~kVCo!+> zoOLDh0G|)i){lflRDZ%a2h2W+c2axzq9ewme=`2rfNA&EakQ%7NuH+=6yiNB_TS2@ zhTW%IK3l@7A))VM;JtjzlkCMVmCs$Wcp(FxiMw92)mTG(W}y^YMx>FJj@O;s=1nrq zy~C*1k#W=9ps2GIF5xw?N2t!!!}c@d4Ky|!vMte@%%G9d+!>Akc1?^EJ{Dbvw7+i_ zBsVh4Jp}0%iVn1k%4^Y&(-g@t#i)#uKK&9A?{~ZI;c|Mo;Img2e6!K^EiBm%VWABj z#?`1Blla?Zs-+|REXlH60H7-PM~E-*7?~Bzq2!^dy4uCXfe@F|w@N-a!5xxHV5w1_ zOz!vRh{vi8)g-Bo-K^7BV35*?2P3MeilP+6bX-FT6y~j9?1j2%YO2fPFk3dUEFnmC zS<8L2N$vP-E|4EX?2B?h#L-thq>1K6DCo0e^v3&gEfPXlCls2g+t)M>@SWamPgDI0 zuBaQN&%4_{TW+wr^>9LlcSZUQ9xh{%;vMX&ax&}>Z6oErCeCq*hQY-yDtP~FW{$^?Vomj3eDGLYvWAKQppc!Nd1 zRA=RN?zJbek@+9`Z!!L0Vy2BSgehzRCH{B+Toq;23xD*+DP|0H6cG8huofgGmR231 z=3PstZTaF*M#!!hc|Y?9q2`2KK2j+InUakf9$R%PHditN^IoYauOH-Hv&b4&#%1<~ zYz4^*a2i0sX|tzd{W}>PWPS;=OWgo~Yu;fx9i#K_scT#i1s{bt0;)rGBDa;Y-$~b@ zb3|#gM{$n)~-_k#jFM&Tdc~StRBn_gEVLR?#^OQ32mt!DakE}Ip z9QS}%>KQqGb4u{Bn|Y;K`jszE5}uHbsc^%H0&RG&{k~U+$@*E-yu8s{+=-9#&9%=% zwGxS0nORTRu5F5=s~N!Z_+qlqhi`#7oTdQ!S3%eF3o*|UnMH7Zo(ciGAwOS%rX-`Q zfiGPR#GhCjU(Kj7pW8xM=xGPWU=b?CIP>reQQ)uCFcKplryb?g2K9s>ZMkkd3-(Jw z{T*GABPp!nZ%?5}nV%_OSN<5-w%HTo&kfj}Zgb{3$BIB!q*6n-ptHp-c0EQ&or2lc z4F5IT7QSQ2q3S*rUdxn+whg;t2nd`57XD7VRz!Z<7!I=j`6EX<;CIspGF|VK>5Nku zyRR?r3P^)kAKwGt#;J4Y!h4R9s@&iGtPv@7Q0k3Zz)E5nX7hGuN~p`vKL^m%Q+3h# zG#27F1vXD}&be)dK5xoWtlF9qdIU*$I-$sY5Trzt^dO2G&C~}=n>$H>rVhavSXUq zq%t6g-0#@dDCX-;BiL> zg;mq%6~j%ca5xik320eqEi&z>awngr{<29`s)DaPBXA*gG&dz#GD&gMzirBVNt{{% zb@qpp;W)NZ{F0S)q5VKuev6@ee| z7s!VvMAbJ$fr)z_;Cr<+ACnIhiY3opvX{bQiLEzihXy%JR(If zjgNje^8`44L|2FJ8OlksaiJnrv}8V+U9_ne4KJE^+^Q#U&E=+R z0lt;U(b;|dwiiD4qhwz6Bo(9z0AnAEL{O;UbP+2=ouOh&u&bHV8~Ob5%?bd8uo_9@ z=)CRaWd_$F`j#^r1-|dJU1Pj5dnH!OO)&jono+?ppJR2bFL6w2dW8^kM`Q8gV2_A< zU73zVS6%LL+bK#M0rp}dl%M7$-$W&p^mf=jA3uuHYJu@CK01B-bZ%%Pq1M2ZSx$WRLXbIKYJHnJPWZC1}gqTTsn)ot7@ZH zz}GDSAR>+__J!PU9*_c!?wg!BT?$%so)Ty*blu^=m&zc`AfMWjQXMt z>DB@$wur&YQdf2yA^k4Ara7!Re_5|Ya`r~spp(G}X`z3c)8&l0YZ5^7@1x01Zqe-3 z;X#B$YZa4ILsaHJ%$|dgRtWn!)%-pU=862a=#4i)I(J{FtZ^_$ZZ4;yjCv5E5>xYY zLLc~rePNIGeiYo76rpP{01)oM7XKtI=9=PZ0Vv!GOa}?#MVj;(WvuetX`RT{5EU`` zkGyouqN?=oY0}GlniNF6YvpL8IrU`&)v_vLlnxHXL*W_?cV-< zi`H2DcWLO0b&wKEq@vJKT#Tu$<+PinBYy8mT}<-tmf-|*~~aaTlV_il(U*B z{2dkabJFVb)SDjNU1E5$ls9`vpW{GNJ42UYa3yPr$67h9>h=M}om!5V(K=>>vK|t@ zMMCUM;I#AOYKPd+=mHrBa^IKdG>62)T!M1ftq`zpvEnV zy{KZqZyt~cOd0~Isue&ar6H`waTAgh5Cwh0n&^3sRI?Vik45P#DKQIoDd@mhCV=M| z6j7(2H^m+p9G#d`8y~n-hu%5Jv^&E>pQpZo zzrnEnKp`I|9dC`ysRhJ`A^#XkW1p8T-iaW+x%UQIE4aPpf0Q-Zp+Bi-i8!P?63BZ>Lk}oB9<1LYx|$cbtkTKQ*TSTtiB7XK zko5NDrCdkQRid;)aQsh&FFu>VBK#xUDTBq*`64d8?ZQ8b9VSv)BCQR8DxNvpnkP{q zEa>r>tZXo8Uqs?$j58!nsmkLaU3gcQ-)2RSu0VCQjXN2ZO9z0%i^$js0%_+3Sf^I& zZ5-f(?(oqr5egQ~S@~(;%BNwd8u?fXnr}*Xh@_2Z=&SbIf*t{q#j7Cb}-50vj zZ`|Ytxu#RVKjpjAD9{=`nU8;FuPXCSrxLj44dU%o0Gaor<&1-Pv3($|HWnT$1YUJVo=n5& zvc}ABC8GbsYN^r3;QSL_xc8c0Xy*KoVS?fOnncvwoZFB59mv4`;()U^-yMRC>HRTb zm>j9!7(CDSa;CJiK*0o7c8jq1!LrrJyg=(^zh%7|hGa|a_J&H`OOJ;PO578#OT@>x zO2#;6-aJetT*GWjfpaw@dGXN;C@oP_(r*fGNG^$mMR@o#vSdqs6K*#!&8Ddr5{GEN z#lXRgh=E~3`ueIWP4h_F&aGEz)>B@9FJ`LO+3xN^N`Dr^HOMXph}^oaRt!W@WG@YMfLd3 zTalF9Eq(7t`=1nJW1wM5lwn#4VJtj9RJ_>an`2~g3-bgsWqdTa{HJ*;i7H2bt!{k= zh15N{e)xgcUKeqKk+3AZoJ?wIAgN^BMQeyDDV52P<4KJHt$ulp(Na3iI&@|$^gsA7=p{J^ zK3Fb16>wl5;F0YVP*YE(zm7fPWu=y9k5ocF9jFY?u^9z*n4kTPjqtf@mT!;IKn3#W z7m@nq*tK+K8zUg=xV$-)AUn)m*0=^WWeanuKo{C>yc;}_E;f4RShsR(+;Xnh!q=_q z@?*EgLfG9~1vBKjs~#o&*P{>9P!;yuzM{n0rJcMt!>b%oMmS|@@F?QeH!^@xz=Pe`+*Ybl3`9m`TXZ(AMq6=mM*5wDph1A3LrI< zm!FGW&sQ3wR&(9Cf5RI_5`TS&FCG>Az$PMQ2l=K({SaXPX;n0q(E1NB(f)}x9oB_+ z!G(_##@vm09m66SQ15|#X(aY0J;wjK{V3xeURTXi&5eJ6#<8aO0AA8OKT#KAU+jPF zyRGXONG1V9#dY$x5)bjGzOnF`Tw>+zRDy<80JR(gBZ-%6>F%~s6gAz2a72REx@ zLuOd&m1Xh5W1Z4kNKq2UQvQsP#Wi!ZDrzOa=(NNl!Jh$R<~dKHwO)|W^Z3nqh69zH z5d3TwzE_KrtgNiyUZplb95v2k697L4Vxq6qjO}}3;F(=4RudJ7$znKZ4Ml*zq3u>q z1MUZ9pmXPQ@NzpS@=RmP*D~jdr8;TWLMSVqgp*-%FoH-uIneWO=m3I5RuIkDAlKb8 zkW=>H#tM-&!O#QPQFR3)4CCjjn`8aNzAEPlhkW)k1+I`1zIY6q8d|{qw1@JFlmd>A zIg=l1O8`S(EcW>H*HT}O{|*P<<};!oK>+Op-Bb|A^^{1jc1QIsWn7V76Tts5YVH`` ztwuN&MG@%8J8>>ss*z`Si{k86`!EB~^fuxNVrujI+_TNY@Lmx?kA^++tnd~^Ctu$bPnkBK?6~*c4lW9BK&s$q(mC`+oTJ~`hS2w zI7Z}uo5i0eg&yG$-Hs}|_#Lprj&}!HZgVl4edz02YWKNi4~q~;AHdk>kE?Dvcy%#+ zB`&~*=tiWWAoLThFpXvWGi@>vu5iey`S=n3@UK%6K}$$W+$NC5pinMO?>&Uc#Xe{L zN$+$IX1LlAl1$^vD;&}KM1P-Ugez5C`tH=AYmP*`#J%Aaeu2 z8{r#_xI$9&L@;}S>jYx6hL?CP!ZL+0Jz~L0cOc6qMkAkgxrcenOye#e}kGIJ4R! z>%J=%4Dx9&7~uXys-`ysa=u&g{#dN_Z*-s`{8jy8jSg}~kN{9b; zb%4qC3)1MCzEm?drxiBha=T#MeCGnTL<&4P1NuWACGKq3!K?=Gq@^90iNmx6f75c= zGLAxLCQQ73df;WD9SG?Ew0|awyx!jxj3}iQq#X0W1KTr{8m>GZ0d|*%lO&C%1Y~i0 zhS<+*y8daU_>h0p3W=M=e!-_m?9gGZ?B!YEG#UfRwVZ1JI|WQp3I!v7<`#!|qXI+5 z;y9>*{G&0NI00|n0@)do>O#>kFF*98bLiMN1vp(2+-NJ@_+dU(fOfbDrw_ap=`xq< zg_tPQ|a-` zSW1jB;9p^6_m%o^=7*IVb~B9HDSkm(6%@MZt)e;<%KuZ}3!jXgb~S%V;6YQBh!oB+ zvENKXE2~FE6D}`Um$o}Rf#C>Kfe@nhCx;x!C_RDcj8H+u0SHw&`L)mse1h<#11(T{ zSlO-&XUv|(wkV>P?dV*2H;bg&5S)xY_p5QN)gyv$dN9@(DErfpCb#%w`qCcYRVolz zKu;LdgZ2)7G+AW4=p{y=zR?Ee2s50$nC)Pb!Tv^Z{y{nARqn+Y=N>>>e~^Aamk9MD zl_HfaNx5=54uEjwSYXNLnX%tYJ_{D3!D0Ob3vK+TvpAv z{{m<0=6HM#i&HQ2LhtY2{NM+rc|@-bik&!K(hGV}D1Dbrw}R4+AvNo8Q?4YLL%vH9 zmd%p(F~2q+4oNJK19=kJz$VtL9q52Phz(TcXV6LIJQ0r<<2L#jK+P_h+~OtLcen!- z++Fv$KC!JqG}m;$d^d_P`~!Bp!_L3_0#vdcTKX^Oauj5;2BZcSI`gAFYg1LMsQ-Iq z+~VxqoUHC^r6!-m9Mz!070nwL52?O%0)-A2kprh+`rP44nWBpwP@j+1i43Z5-_dhk zM(~&N(LG5VcNzXg>vR)Bh5Ow^p3N0>7_pD4?6ttd@t+?4=^`T+LcXcLyQ$g`V4*-- z^Y0;^Xb4=IzZvn$ z#*89=;?(`k6H_tl^nb=K5Fy&~M!s+6?h?X#&gYnD5n}A!jvP?c1PD)YZB&fq!XV{# z9HR7#%^s(F(t-stn4*el+&QW{K06tNhU>2&aRjoNM0GT(o(O)S!|J3P&K3xKOZ8_n zgeJN{{bS&8i1D_GZ?#zn%R&!r6&ne)Wz*iXeNA4-e6SBdmqS(Q%1HHi&0yf%?BZam zJpYSuYU&P1zWT@XuhW!WaHsjl!}v=si9eEIqm{_h9H@$Nh?lcXw@af42l4MC_X3%K z#?#y0?NzAZ@4utm__b5fNL^$<1@gcmSakjr%kK#bLUg<&lOs`0tp^=y0~Nm{&~voL zH7Xq5G8*@ewP20TmHMDMa`jU75{HPLU8|1ascu{0ptid(r&IR(U=EbNbNmKPi@T}r zj$w?wWk&lCcQ4?5y=RS0=qA|K;Iyoh0St`um@%my#n9v6bG5y&)Vcz&O1GJFcJNuh z9FMnX=@IBn=c%?jJ{w{`Crb!rsHEZ@YqF`fMZCzN3tl*X1J9vyxPF0F z>ci<-(m{X=60|77ax*9F@(*11Od;#Qr)kUtDGOcxLrMue4wU>cH-5dM!Akc60po1Z zfR{EP7WiOLsb5M`*!QLo{g8Gb><@~C>o>9}7@zlK*R+%^6UBl0vtj>!tsUTg>cus( zTzX6;PD~6OT0vAhGq^6K<~ew$19ikJNtu@eD+`$E@Dk{0|MG&-&5PmXW_JOHj|dr@ zs1zK;^p<6T^@&})-u5jv-hJD}(9#~0OQQFSxcgID2;=0XQXRr3 zlDndCykS?CWra2ZJZv~Z9|s!4W+jMT$5&1)s|1=~=4u&7RDO1M8^cr~ux(w+;xlM` z5!rSKV-~aRpaTYxFmkt8#(%?@lD>|{dTHX9IsayxL_0A#*^nne?qid=+!0i zp1q&E49K3tUGUa~zfS_XJ~9Xm=M{icyZBuuq1o4F7=6J)FjwMg$|pGps?{(8 z!0AQ}=Z}#JxQRSY=EGwB{Kgx=YYsL??8XV$Brszch@wVOZfiDkiEkJKjBU)__g`I( zgv)X^5emNcWdIg-&E=}`O9?dTD`P7H*5UvBZ&KcfM^6B?9UefGDyNxQtkDEu_KC_D zB_5KZiAJ|vHmphSgRGB{wTo?D`(+>K;KDW>%@farIbHi?485@-0hC568;=Z}zJ#A& z4+@{Ar>GF00&u2M7Jztl0Irzp#%kc8O9fz`AqS1HSEdiIvjIMvW}L}R<2~J6jMPj3 zfVAzBe49~OSon_$OWbhE*hn_oF7E~CYo7G}2oRwzi%}PNt}@(6`h=tpd-2pHaNI&q z{TKJlb&6AvY8m1sda(WA4OVml$J=WOBLO^ z>w~|ZN*%a;LAU?;@dmCe$ZT52K8c0|#}>Rfoc3Gm8Er>+u2ldg?>=%~7P00-|NTX1 z>lZilCXD5KD=DrLD2(__(;!fqIQ;!5e!}MmfShf<-$)}t9LOu?Prygp4gU>%ypTu@ zDD{8c$+qCnZz_R>4X`%hm6qSJ4)hp(69ht$+m~51V^$4C$Hy##@#@Q24oq+ZfKKW>nm2*)no_I>XwH2Su0DOfx;H2& zt$oz#+o8)j%U}QAF}AK}{u#eIjkGP_|AG+g{u{8LqQK4hMXRh zq91TRQw^g=| zY31&VvaXK>KZTt=G#SjWlt7nIul6q4I`R9PMg;W1pNeHW?(oQ$>;&bW-9SHWGGJjL zFx8RcH=YJMUf%QsX@mc;aL8+_@9vTESC9z~FfYV7S0xlC^~$x?(}M9|iEA8XY*OJp zs+H8XAD>@43TdM>vRt6jM)(R$Di&kVvNHf*W(a%WaCzoh$O-nk+!dD?%0xlBkbW4q z`mmlP?w->I&wA8e>79kHwg_8*mWhtFZ$J<9ZQdg6P2+jIM~+GHe;&wlx9JRQsw~83 zmC<~jz#5|F*{0tf+AGr7V{RRU_LVmP%!elzA;!345o{DJGH}1cJQDjEjMkR2bNm{( z++UkjhD|A{Po;W1H~$s*FX;2GS7h*3wJEmLJ~ef^JBzIIf8QP2O}4!L^ZC(KlD-Iw zvm;L3Idg7^UcGO^2_3LEv9Uvoe^eubbK@d`O@uRCA;2Z+Q5&O^P+8fpH6|OVgq8PPgNs@+jWQmfz&C4kdBs-J&9ne( zzQR1@X4)l3a4X#*%*DjXUGigp9@(|*w$UB&4S8Ri!dPu@tQ`TxL>4zWlZvN{7^|~8I@=a{I^9Hm`sc}-* z5&|DLub?`XU)Fryw{kZ1xE*vNt!6t|emWE#)N+FMb50aqP#y%4V_hhrX`Q^yvVwkd zc0GPi)c4B>Kqif1zY$Rs$dyc{oNh!S>zn>+$@FvpL~LJ*XSo$Z2`beW*xx4-TsZv# zS}gKJf&RmMfPV`t)f5{$h>F@|YK@_Y?XBrBP!|KvsE#n>50uBB?9bH76%eT=ei z8u{>>&nwFk@4Ik@QrX|CXGWE2CKb2jLE;=$VGwP#6hfs?goUMM&WcXXM5|8grIzxa zl+uS04WEEpe2$H09^`v$Md*lw=@--tjlBmo;oKaJ$qK94t7AR4h4z`08s|!b&-^Ml z1zA;%oenm$2>a1rL*vn%S}5^CHcajXXMeu|f+74)RIYhOmQC>peb2;_p*xvXmIB$h z$3Es%2P+g*WeB&pCx;2`x9}g$S#(FK&~6qesO&PljA}oLV`EqDUq@JWOgJK8zw^P~ zQwMGnM12TcUbRAZyZ1bvqwDv0T#q&Wy!&U}!q zb4=2<^!36tke97|(IXV%>Rt@s`Y|gzKr_(@?)yP2q2lE1)H~kTUksCP;06BHFHwfo zh=tXd?zm@xuW_)oWuGI)3FkHZ9Lx+4ay)~$0>q}SKDx7BLqCqMl#* z`B1DlaNTIEDf79hco}zMj1k&V)hlGDK>42*AYHtENW8`T=J1$$LRIGi>G+=+Ahq~M zxqa&n*67-qL6q^VOo0fNhspX6OA-kcQp>bPd3ikZZzq%KWXXZQK z=iTnrpAP>J?w9yE&&R)IvFRBrGxfh#lJ|YB4ZPN3E%zn!SIYNB%m&CE%$HBGPZxx}~tMQG+`V?-l7a z9_47d8!mKh4x4ZD*Aex?WkMU)TY!_-w~JVM31IABP`Kdv|47rF#^Q!fU|cYfMmzY?=jop#!@PRd6f$tGngqVr_52A4W(^WKNb~!8D8jnl&50&sw-_D~wmna&n z4Pru;;7W4`F4*__78t{;z*Tk6oxtJKpx7?$%e_~sGx2Z%*V<6edy{euNMjrvb51`HIzL@{Fb@UOT$PyM*K^@t+=GSWO)yh0^rvqS3eW7>4k2BWCV7NAH-&MmT6j zs*>rZsuc6C0o0#Wm!b$E`J#$pZ-lQxmInZ=^g(FEO=+{1#BqD=+KEt%-SMIEc0x*b z!?ODM#(ORlSFNohFy<$K&2`O8J`l01%N0Dk<9X|Kccn7k)d&=z?`*zP=nhyCFX3y@3{k+@J(q_xOjN zzZ=bmrJpPDIib&*P!k+l)ZiWTyD1=eVaNH1BJkDPzYBs)xE#O5ZtVc?d^=61Af*|@ z=#JN*AILks{_a4_uP>Q>Bt_5GiYw)3Tn;c;Q(O?&muN1{((B9yM~(>dmsN~S+O7YD z(>gm2Cxl9DKfr%+3w|9x!Gi7?~yGt zSGL60Dzf($Wklp=%O)c;viFQKvbREZ*6)3P|K30D9-tVU=O7wEY{oOv#MbR*vf}`>z1Eu=OInlI<2z6VI(jt854cZSZ-h>jZ?_kWpjDd~Mv6paN!|z73Me|QOh)=i0mS;=cJf9d4br&Xn++3zuDeu% z!YcZW7vIs{A@stR6XvC=3Kt}l;QsAB8VX$8D<@x!-{oXgr1*IV05eZ03Ra&vRe;s~ zHKwpgpJ=-qO!UlZ12&MQKwa3xv}I$s!bO~F&FD0I$T`+uV)xWhj=wb8X(F;kjfYcT zQRbd6mm{LO#eo5Zu41%)<8gF59+!fQ^Zork*M>KG7DYPQ-@NCpu2b7fS=ao5M;*^W z1@wS9#(ceV?#hE?NL=`>Z?ah*!Gr}_SCL#dGEx`2pT)!QlD!_<=ymIg zLNWb~gLpmiWR$ti1cElbiOkEj<{sq@UQXUj*|NbnuZ$H`a9_7%SuQJtSaxO=`u-+w z%jTA+EmLopsq1b6Sm>R{Kq9x@aI1FrCl-0d`%G>L`eqd*wh#3I&}zKeJ-SP;=RkI{ zzbQP({>y!QW_L;s#QOx=s0aUDVa23G)c02Zab!<92;b_~Wo)_hM0S&h8H~~X;UBys zj@0{JYNC%eBl%*)L*cJvylMfDB5Fg=~LdSI;#X`ElBy(dEMxr}0UnU88G@f0I#r87#t({d=GE4y{@stVS73hfEWwg$;x(4pWDL`Cig?wMt^=n2{c{UR)9?4FL%ffQc`c^gB?%v9E+CL^n(6S9 zoYNR%&cRZJM$$jiY;=Yl4)J?p(ys(=F1ywVQZ$?T@9a_{?IY<%=b}B>TFWCBSKYXl zAB{)iEG?hcDkGJZNn(PvW^Nz42Dan8xFmhfFUfzLN{u?V{gof)8v&>P+R65OrE6YP zRGp5PkZ?=*={G!ok&$D=N42)fGc$+Zi1}7SxBYU;2<#fK7-L7{ur$n2olmE|34)N*m>EpxN-Pih4 z$VtT47RqXTW8gl}zHDrBjrZ9{-$LM$+d%*@dhbih@U_lK9p7b8I@D9e((Wg{t)3vG z<+oe}NQ{#KLN&9o>ZatE*YQ2MXQl70#OEr+J!Y4mN{e~;;NTdOGQL%jT$Eh3V{(=< z1?7RcgOQNXPymhlsEf@DTHj;I#;+UDoG}nyDhi1Jjh#e%N|#fY?$!lp9t4c83(u=? zsJ|Kj4IfcA%6fluJIN99?s$e%ZfLDG+7WSu$l)hkQei}NFLgwEIq@b@ew$#@e!rtl z=om`@|ge-_2&G<^-|DmLF`;8^!Ai!Wq`Q`>Zoi`mqT}P2e+^CHjUg zd@dF`rA(_VxH$D)lyN2WU|Wq{Ov%rFLN130z?Lt4Q%(|CRnHVG{6Fx6!=Sx7J+kmT z)8;ROu*2x$|6g?w_QF^ol?JV%SUfa;w85Ea#M2jXu0k22vX432W*%%R!QH5vJA4w> z0@8Q}a_7fvD?2m(%r~8m3W2W2%MT|)Tl)F1TAy_O7)arxvW9#=o56ajLvI`W3=Qb2 z0Tl@ei}aN*_sSdq3dnAnz%+bu;ir9*n&s4ZqEsudkLL28>Jy~-DzkC zOEBU9o_mYjj!a^!Fs?SawN&o>LYInI9cwmzNzj$A&Hv{L-X*tXHbQZv87KKo!2_A| z?NEj+x8n40MCQVO@qUOhGZQ$UElGGwk1K8MkirPd zEORP)dY>@2-6@q}?CP{Byi&TJyR+q;xw5|$5u~8(k?av&FmLVM5La5$Y6BYXEW?{_ zCtr$Fvn^*IjxSouFzTpp7N_IG;@CwM()yV>AgLxb7242a_Lp%s0$qW_r!(l?$Lm>n zlAi7hAQVQa?QtcUxw6W==FAuGLg6JJ94kbS&}jxF57iubXwvX`CJQ^y)VyLK@(p^K zGI1L$2#eOv z+|w`6{F#`TI4neHyi=D2jPYrqZmN8VjtsA_n^tsj{FSyKu#;TB`lqFizmSWwuJck% zAD6y>hcy*37SCT|Ki{vmkOmawtDs*XjmFA<>)mf`{{2Qxw$SgPLDciX4KZd&s@%bd z^q0(HFB#JF2aD{scFifsWDOj>HgO)fU6jQmfA&`Py%Q$bbgGG0OpMZMzAvI2{e+0y zXKN0m;Lq*ivf-n9Ivd2^M+q+MmbXmf^L+(U8^z_h{A<+LvI}At3wVz1ed_jYt^OE7w=WtmIo2_8o*|N6GmFF_Hd3v4|#N{E8_+w+D*WWW&fbHK0 ztSiedSS&NtRu8hK>bMf1))f+*9c98q*u8CYQ~tx=Hdu@&Z_&bBGXH(?GBzswH4DAx z?kbC>ADkTN7f^l*rsBS>e&_y}B1Q=0_Jx|FoWbARGyF@&)@v2KBsZR^=V)!5<2@T| zD_WXV;wahAh`$wZ{lXN|QJ?~0-XT;a3M4$+TxpdM<}jyuSH)bm1&l&Rz}LAJte+RI zU>H0;BBpKG=4EwXP?X>5g1^rY=*Pp>qrJv;F#wFfDO7q||9B*kjpG>AzvLLxMH$mpiH^gx0+H;eU z2A2!rJh<~Blwpf+h=Aa4#Lmyo0}y#;$vPGe#gNm9FF94!j(vR}xy8Ku)h09Ll6~0zFjKiDBEEgfOfL{IQ3>)`+ny@RXti|Lwr~}^ zNGLxHje&O`0zMowN;gN*@hG(4*c->i!wBDvN=(guakr?ojgvk7X!GUY`H)a^n}=U5 zKbRtS(waNsswi?yUVix@q;t|E#(67xT-HR$N@^g+{8}E!uH5C$J&5{}Bgj}O*YvC- zv@}1QX;UVn(?5zjUMT!CE^htS(D5jXQz8N0ovELx>oEo15EmFCLPAEajq~Ukwy&*n z!sPkAiDd;h9==5BL;QJ@u~f_CSdT2{_*~iNl6zuBAn=q#j z4#QufRXvx^4pun%qnU&6v5>CqIa-Uw8`4!IFDpc8EL#{6nLXLc<&wbnIHV&)0vU3W#VXP)G#n+l#!x*I9NbV$4Je>i0 zC#pJ9TJUO~({?!;9tSjkB*kL_j7cR+G_|aH%|lhOPyo_#@dLwmAxn|x07d1veq}Zc zY~lp&u4C`SFC=`#o>>dxhAc??S#E!>M}CZP!9w9O!%7lc$kfnp;#tO49H(Q*R3k)< zthUQIZtr5jUQj8YGK-h2Qt4Szf(eLD@*Y{0{&BEj#8Jdp|Cxzh@X6JR?R72GYPgij z*cqdIq)tsiF;3WUU4%l}1t@Oh;cfApk=lizdThE=D62uYXYIgs=Xb{sSnUDXdXHSY zwgpH5jieE9Wo9u@|Mvk&jAn(Js(e)c57{tEY$1t&_jL($I_B!#$e}JORLDJa^O~K| zR2*X{+TeSRX5r62*#+1PzwL3Wv%Mu|I)stA@wGYD30mEMh@Ax|4x({fwfZ9H9bz4bVw*KRmpy40bX~i+{ogzd=TEy7+DxFMKHqs zDJT$$TQdNcO$jti8YL+d9tM$Q}h?UElofpyg-b7Mnovhh5{}Cgi(#BfI1q;%n|g3n1F0ua%V7a(7)+ZV%2=g%GW6PuV|94gbn*D=Q%UP{UH3;PBbKoBV^ekfc&jY2(9w_9z>ot7YIa!yl-p|h>E3jGfm&h9>A2tzkc&s&<%p@>9`%RK09?+FI2&2zz*MpfCy7XwEXLww)LQF_6q%d+vJ}=0OibYx0Wr87j`nm!3>qbgOF|nv@jNO@r z5#+8Hn`>){6B9XkoID^kbJVqI9z|!H?J9VQ@t}ht!q0|)E80ID^d`z)XX;cN;ef)9 zk4WV?yQ%IEJQj~Pv9^C+e_UWQsdBPcbU-fm=Q-1V*I0rjAFx&vzUhBR;b@Ne8MGg~<6b%FdVJgT=YXFbK@P8;sz}HIMuh}3F4oE>- zQp*#3Xo&2KFGHltoS!p$Qy6hcD?=T`Pva)3CLxnv3hRO7BdT*t3{x;%K9lf%6U6LG z{knBZiVJP3P=PBbNLOw3?^7Sg;nC5Lv0wD9Cjzc*FHgQ_%*|aFw+pm=<{U$6gB}uA z+kp)v*)s-ZXE|^Pt=J+spdRQb`rSG>k{h$kZBRRCjb*n3SP~skT@tZq9{A3bJo+qJ zqAX>S;rApP;I$gXOGg^h^62+?`1F1=6?%LH9n*ImAsdv(E;890{NKaezP?Ko`@S4l zzHXya>asm{dwRJyE&i}qlJg@=)aTFnT3>j;E@HQzrL?kU-m6eFf+mNh)~z`4$L6tN z*W#kGjAre*Xivnf8}=Jz&bQ4@DdQ32`-H+r55)>luhl?g%K2*3zL_^hUR%G|VNMeM z-8d3vX6sraVf?PfR|mfxt38h9pQ}Q6TR_+W38xE*t zm-$X>y`j>tWZnV~=tWceZQA9Y&dwPd30o3j{7u%XQD}*=7Y6)|mLRd$i*LMPTd$jO zs1@*DG1$7U+N3GrjSu{~{1(pt#`NbMV?Ot)DaskG1+%k&<8t8yN|aU}E-wnMM$Px4 zaZc%9+MJ9WtP$tm=R!QtZx$epK{pFQ9*+TT=r={wZlGjV?Jv?BgE1NH*ULT>X6EKT zf>}SZRZ6@M=jz^Z$(Bspq{Vpx4|9BCVx8f3M%hfN+3hMj6#iqQyKc)0uEI;>AK$qN zFHL8uG}vxix(QeA!Jj)$@`Hy9(dl1j{xG=w_oJ;I!lH5ge8O4Tf+d>SgYD5OCC@p| zqJdwPF5-2*Nw9>TLz0sMIF{n2$M^K-2T{0mvKS{zZ6CP8`*3%hN{5=8n<=BQ!uxos z&VUEHuLeyHhI6y|WTT&$R2{-Mu*8827CbH#B0t^%u8jP64=a5B`30{q`OGE!d6Tt~ z_g;}~{$J|jqR&R}H=P#q-xlv31 zzZ-nF<0KgWtL@#flOVfg->|I1{+O>{Nc6ss4S~7$}|A(X~!hn!{w{)gmP6b%eE5#QQ6SPmHhO z>-Mq5b>~MOd%2Fx7wT%`cUhp>4xj4}Tp(VMR@#D_2YO*KA#B$QhSDXW7bdToYd5;j zq!m9^r1W{I)%_q=0Q3RDV$80631X86r5^bpRk)ICr#UF&-l!FC1uW?bu*>J=z_ds& zemDfy&qHvIs>H!Y@XX)z6Ncu(xj<8ZAU_I|=hQ~8_E39KgVt(t~Uhdtw0vlS> zjjCS=*z0#9cdq8Q^9|9m1$DwB=Xm3#z zTjKK0;knPF;&xbRGnorp<_NmMhKTIB2~!w@ zW!*xf}Tz3O}+{W!mQc46U5eq=22S*BL(b@@+8ZcM5gQ(3Nbjz_{Q3d#kO zdK8}$`)@xbisZ~Cv%TT0>(s%GJ4DLK(y`J=ytbrgXxW+c+>68Md5$x{Mg2)-*h+7N zS=wFLa^{P}om;5{qPwKhH2&tXWH1sY3bHjy`GydvzJA*l))$WbgwlEtk%uuwk=Kk_ z)n1fW(ca0fEL3=Zcwx)?iV$2lpF^VY$<(J0@56`H{T28k!4br(RCGa zD6E5?G1nz6(aFV+rUK=Nx#Qd7pi^7I6@{*ZrN-; z6nir2`HCU(9dJy1T7knf(@8`K8BBc4BnrmhFpVk9hceL>M$H`3g6 z-2X|oF|?k^cww*qUfpV1`2EM-?^ra)lV4-x)^V^&rZqR7FJ#&~Y74BEfUgD2-lGJ( zDwG>xohnr?_&h~IK{u$(KhE*C`IODvThR!cT+nIP1y+M~=)AGMPLA`%F{W9|+pki) zT&F5s?@g*K`F^F{5VCzFw6UdcEn^7u%D78%@G_~rSXr}C#y!`ea1<2> z*APnn(&I29f)VNz@T*L-!~q`^^Yq;aL@rWCOGj6ZLCl>SfcA4}hqmkC&%#nDIuU0I zXcpKD9)uN|cPZX@I||9k^!;ZMtd_VwR77JXgTH)qLO!v_tQBQj1Z(3`QZt^YElumYwH_*D;Wg zX)Hnu%1~9`!5V1_UVS;T;ItCPk2FRG$v3dOU7LTlXp)dHkYHWN zMqAhG)EX73n`Hs&`p5tGGq3qW4Hep;BJiJKMWg-2JMMCX4}dypba_hy8OcKZ6vp)4 zuZ2sHY!2#Vdjja0g>A_!$0?T$m;jo9CH2MLa(a#Vu@XHUV_9v10@GW@ieiv9$oZPC@-m06zn4dEz5?a_}@HyJtZCirK#YpI_n zlLi?UaBL4G)|v((c(}Og`=5>dg*YPK!OZa|Bb)# zT3V2!>i?B(>*SohXj)QSl~J~W$=!I_xF4c zCH1gM>Zc?on-w`VIGVPpn@4=wCX8ntK7l#9SEJ{)xxIwGC&H_z8;?=%vyr3A{>|Df zF@)VPZBIGc-Js;)TIa+9Pygu6)!ZvJm+Z)RX zc=X!)Z5+_ybhbXADSm(2*BL`5@RbUq6m2;Dxvdq~s5VO%ethWChXeKX3NMLAA^!+rKG?huj1O&gl$FoI;8XZ?U z@v12(HWHef1(El2aCv4W|2wdAhSlNAHReNB!{hCV3`!}(DDhj98-3u39OMOXF7w}) zZo4>`?U&mU<|NC$7vD>NWW5aV#HOa`PMlCe{6dPAa@~$4FE3BLYWXKO!CY(f{#o~) z+n*4%#&2nS?;OO@M%C~p3gqIN;jbIsIY6(PfqledQhDTpadGj~1_MyP#1&{X8$Fsb zVv^ggK=Dz8-anI8(R*PxTb*E}j21WEv_5hJGPyZjL$&VyCci!pMLv0z!mf9Gf_=*Q z`T8e^e!X?9LRVB z^1j;t<+|D(7%T`;&3JKycY40nKLZDI-ydvLdWf2+sp4|q5USz9c;PaZgdADemsGP9Hw&3c=mL?LVCGojEO+?6VzjG|BJ*)5s!$7s9ZO&ePVv?|Fi%nc5t{DYIXil{T0V7 z3#s1P)t+_09w~yNZm zDL{fcnn0$erYUYXcc(xnjNwwtm7r9jB{KW{)$p%On+*-A08iB4c^S%z;D3Eg7=y$X zJMe>9cFsWR*Df*STpNPoj%R*<|HK$KJFP(~v=g7<;gu9Hc_VZf{m6qIfc(TZZUaCQ z3Cn|SJ??kyAhvrdkZztY8i3-2+M(PSAQ~1(Uq5HLi?6(D+>4H!D0|2AM<~wZ3Mk^V zKW~gY$Nz4RKUT1vQ06BOdT}S8q?9-zR?36GJJDM{Q)$+q8{+)OVONgy^<4acRpc_5jd2M8S~6#9u6Fn{kG{!V zO%}+hk_)@-{)BTVQ?S9?&^>^aSVhfax2FV+yGxhH`wvEweg9(f8Pue)c$LrL_~fPn2-4?zz5PXuW&aph>-h})t?WJ z7b3JqFO!E#)sVAGrTv@$$<9?w8u^I!Fj zNxWLa1MdB(Pj9l1V8G+O>PhvSyPA@qGj|nk7QX2{7D_e z73)=9=n)YSp85I>@1JuTclxR)e%@6kwn_0tfxQyRBss+a@x=@5jHT5PBVN-+23_iv z!9S7PbCjW&#wkOe%pVBdX^r0yOtYNuw)>5=*liQ5xrw2(8xit}&Oi~2mKWo>?XlZH z68Ssz*;=_4i2+sYCvB?OpS8thT~JrV3-^7k^j6D*@Gcss@qA4REmK}s)l8KE^PJZ!S9Mx1yLasP%y)-uEw$1=3y|3;Cft&$B znMRS~b7h6tUF$`aNxFA~-J+j@j>AsZX$4j{;>0PV*jZf&2?>!DxgZ$A9MYSoQ7;JB z0zUyod7wx?EU3Oav~X*kyyHTQy}#*wzY)1(?q?z9{O~k!w$s{W{%Bjoi8Lhk7?nQH zKXoj;&}Wl#C{4fAyjDXxRU6)S-Jg##hy)|RPhyye08ZsA!9zN8G=vHit?3#OyJfUo z#S=^lE6rENgjR2B-6P=Vzdu8G1t;w|4Cr`}F$b<;r?2E+!AT!EQ0Cag(5fZETkZ*U zcH>K20}@NsQ0^^J{z7qMxS9t+H>pNv3{^bXCE;-ThJ_$&O7~;Mc8vW`l;r)#!V6heN5d`zhuw4LkM%n_iYl`0aD<1XYnEr=7liyz5VLf=@g#st|+jaR@1>AKHuxY_ICJgMDKGXxw{ zIPcNH(ys4MXG_h$O}CwcP14H8D>+UTo4&x+4d6B=gLrohS=;}MIz}zwE9Zz!2RJuhpaVR&ydCGwHW`M?Z`a!om)2P~{ zr5SJ%x|NN8tB$l3us=2 z;G+-VjuVp{|KQ52l|ZR z&o9~sHl7ydGWTm2cX-QIX;KmFyuFDUFs7aeEmnspJwP)uOQq8G#p@{z;}V%RtvHU$9e=nzm=Ck|WJTNf1W& znsn+@#r%YkO+tT{0O~?Dm7I~EK3b;If4_Q{tdF)FV{Cw|79`!y^Lud`kbx*^NimTT zba0Q}dTi2O(wi`WG)sy~M}^sEu>U>r5(6rANTe~8{9pQPQj*C2;lOE3cC|hY)aGWJ(L4Yh3t~4RMeE# zacO^=%Hj68kMa#SQwwN4ycwnxqE#G==7$?rA-{HotbK^|;n-e`fM(@oWWH<%$HGeJZ&~My#*Mdbj z!DkwM8#6;F*cvM*C-(tXP{v-t-4;V4{kt$62PI3r{;vQ~%+exMOPwzzOFJnd9xce& zLlkXAKK;ExryC#b0x3H=DC|<#IkkpMYlqtkGr=BNb$`tF+34r^;|Mx^aF&Kp{raba z037hWeYD&2_diRaTk^ek-BaRuTWf!k0X0^;TV8@GE#>eRE}94K#RehZjW{;8djWP2 zpv>n1HbXQbUd{_OvvgjaMyGHwaqP#y>e~ywvx|zy8$Dj-Y!NSE=ri|cNcF!gZxnCm zXgd)Z3maZ#3p%gU&m1~_v6ET*OLpotpnEn|0{=KQfYP?HTcA3QNDPJzXb8KzZG9Sf zLAE4g>lOb#%~Ayn3A!6u2RM;4T?UMuq=8Zm*y3oc+l!kR>r~6*zcz;ATrt-!`rhHb z`TF*j1-09?T(?4(r?X@|PA$Ag?Gz&&mQo*FjdK((cG>*uM5o8(nUErzskW?vr!Zwecj1!K@Wiyjrv})DHilF`fH_s^!cUI zEkU@<>{yB9+8iAkpNVjM#B$L#(0RvEQ8iIeFLr#@C*NwC`g7C|e>~qX{h$v}1zKXH z?B0maWHa@?jvZGs3*5rKIdHopzuo&S0E$K~p~OChqA?z^FLDw9MsC>l=fabZmGw)X z7%W;<7Np#jBYxVA(0eU{29a)Gl`=0Wq;1k5{dYN~wX)=d;YZ;Jg5m<}m%qtVcsBo7 zqe`?Ii;A8jbC}R49`nH)AeJ#J?CRCACrr-({25PE#KAs6tPkM7E9gCcmABn2DTI=b zCi0|wvXQxs?3dfUqHIAVsvaO>8o8)li@^kdl^VVBCum8Yk|!gALfLZ#?r~(pajmf5 z8lw{XKUoKNgID~&Jr`8cxfpX|{68>dp;0ZyLSEc^ygPt2k`ydp z*y(#)t9$J*!FRg17+>c9aEFOSA?z02cnhe^mA7l*RI#r-Ex4}8nmvuoD5x7&`o3*X z-+eq<(_7C<$^ijD`F>9&@~>VX1bVKVDd^l@v6P0+IIWG-ZW0jlC@dTP`v+LJn0Xl+ zrjYG3FM>f%z6Q9MTDaFQaP57zc|n6wbZ^_cZq}oGi#3c>%I*GxQ*T4lD0;$Sef@Tg|CP?r@F@#Z(|4$3h{IsB>l^iTm1Q&ueILs9o zdB~+23yiz`{VYam04wg-=|>S{)AjX{kmLY`)Heb=K6DA1`0Q@s!ENLIXs3k!ap(mz zG^f`JrxQFzA>18S+qQG4u&S~1gU}A|t4Jv>w!cmW#4$AXASRVe!H?cy^iBxOhx~RQ zY`k=vI*oz*QUVR>GS~5kdKzO0{X@xRDS0lGd1SeAag@$zBQ?d&8-rIp-CvjxN4lKG zMXz-^6U!m1W460WsN5^ty(n6*lCGn;I#Z$T<6%ghGf^#`sLbam71)h+d}{(<6+d%6 z;IAwqI>vRo6Q&#Q9d!eGSM1ZX)gJn0RDl|klMxQFS~F`#Rb}POY=AFR@aj{Y{&yK9 zY&rnaeori#$(!+V28_(}&d%g2H^$emV;K;VwwjttyK%(+SS|a9&}348yhCs{*m z5?2Ppr~>ZKxRt9WRUsxOrD6bYb7r2tw57bHF%eEL6x}A0Pzac;F$g#aS`2|=KRw)(*Vig&CVs_^vY<8H276_ ze{*QDIh%*B^-eb5I~CefjUfi>Ug8!b?~NFNkf(`y3>Ztw%CdDz3GwvnEMa%vW4W71 zUN7f)B=FWkAo#%!$E#fSIC}NvCja-5^PkAMpFCA%cNmP|-F#g;jTkpudrtcs#{nuT zYG2pm-9hpo3Xl#EnNIM`@EC|B7-d2*LEOy*tpEFbMaq{R(ZT~rt^k#pY9;rc=3JBt$pt7Q^rsN_FUurdV9DqKt2~a588@l~YDe^5h-qY6^p066;a>uc71lLcjD;&a;> z^3`dg#(PWeDB_VT6y1>6#YTIBVKM3O2DKXX0$G+0is2EnOV2$vu-$yU4SMA;_cIi6 zSrsF~geOTsL^LPTgJO3b8A)}b-f)}!6Ylc?{c}p<raTiM~G)XbXLjR$}^ z|4c16ybsH!miGRCvt*}9OM!}IW`S6V4{--fDg8~akC#%YG^MhFD2o9u zL6jomV1uftC*|Omsjqjt2MbYHKEM_&)ye+xMlcQGOw~lgOI;nOEG2D;_-PT*AyPpt z6xW8QeA;wdcsUF660`6>eSUjwW-vzed-RqiPX>xoYM}R8wXrmTvZc~coM6j zAONUW=>xGVZ@4A;Y6a4?Dn&yXuDvbBtVP1CB|@n&5i(VgA5_0$lKyqzxmvD|gP`7r zo}?e+M5vM1(xx;^+q~U)8$M`q-poTpI2ir~qrZ15t6={G;qlWP{~xkX=qaUrz`GbJ zxpJ*2^~_s|Svj)J^5uhXI@qVy_^%L;g9ek%SOjlad-bpXAX7Rqfv=^FgX+)m<;Nh+ z1`>i_(s_i@R>|_L1kFi*#{m(pgMgB-?01!bLJc)mMJ3iVl6{QVL(#^k|AmS`y1RnY zs|3WAf^rChmI4rc(Xw1eS0uZTK76;TKp4Vq2;*UG^PTI?)bA;&%i8Ib{^|2m7RJu? z`Ww#Ukop#)^fX)rQ59qZbL9y5(^m2P&m8TGulb4Jj#cu}nGx0@>f_(cYb4<}MOR!D z|CI?8wPe2=+Gd`9x10-PBFtjyOLQvrQ+}u=lZ9Lkh@-_%n&FEQxaUDRLpDgvNY-Ti_K8maj=)VZW?KzQkjVHq zKV z>J6N{zQS!Yl?Lvq*FVyqm%FWGbhg98TfLXwqBFS!^?HaOp3eVv?^dS-&6t09F;&Q$ z3x{=5l6hEbM_{1oz6}v7zbrt!8*z>dY&kAP1Wgr9V@@z5-&ou=7i&>^O|%i%11^~@ zcF4Ql6J0*krm=3GVWUaD5--I;xOEp6Fr+N8=rRTd5`~^04a`7XfN46VH1GkDthWG& zcJV#v25Y;UFdj!W3OpRVynK_)6O`t6Cl6mBGi|0(x67Z`1sVxlptoGAPz@YDz!`I3 zC)}iCIZR2$p`KNefpc$1b5#688gQ9Fsn4!5z9r23;yJESUr*s*u|<^PrzvS{cOaDc zj0aSo(rfZ;tK1B&CAzFkSVN&twqw$4!<640o^*GGFpX(Kw z3Tb@Oq{SN7;WsK&f<|HS5csEB8HPD&l!=^oL>n-@Z_{;qO zX|Y=rxBZ6Pysq1X`Oc3l@emG))>~ZJP+9=8Cq9a!%666i{d4q^`ir1IQTpotwM8^a zL8n!d@)k}XzRP~9SBg$ zl3WabHBDFx-?<62o32XL-729%82tK0$CKlMx7)HFr7&FI+|j!b7j#h5$d8u#Z1-7> z!yX1#+u@U#gD`n4rH*~vkL|ZgV+V8r*ePSx=5V}R;b@_QmB{BGkf>Z9*1ZoayGPfY32#6w7$bl3+x25kNKf#8FhgpfgBZ=N_ zezN58JAcoGOW*>aXECm29>wSZ`8U;o#AVMTy8AG~R@JT#5ZJ^(w|0ejT5_FoXJY)K znGQI0l_k6I(R`NDBUw-9T%rBc5%Pai2gHgO5y5hByRZJs<4 zK>aHbEYG-ma}GdiEadik{TMPgUQFjb^V^%LJm!NzVzFG93iJP11rQO^iB7msBsNrT zlBELWHr*w;<)Z{ho&i0;t}m#oI6@_E<1Jy-m^8B#eOF-7(As?yU_3rQf=7ZKWd%i| zX$^oh#D4i3qOC9yCsJX;T;7IA1ajfcC*z7+pHDdJ<2*}uii6>UNYT;L5<4}?C^nsl z+y6Y;_ymnjGVkvh0s$li+fXzPzB3fQ6Im2r`?u)VXXY{IV@M$7-l=Rzdm=(;cHf?D zRmTh(s*YpF!x0drtOTsam~2g5gud#P#?K zz|EGK#Dp+N079_*%UQHSLit9@jzVKowmZT0UIw5g;rol#YU6WftowX-V74k(vk5bI{Mov&J`Oyz8t6MGf z+N{vB73H5S+Ig87Pyw)fY0WOe|I-2}yjNsa{Qq+JA>>C|^j4T)c-IGBrZuy2ikkR2 z*S-g$aob~Qd~Xyh=yHq7-U9OfWYqBl1G=h^Wsl45cfgkB+`N;Q&xFXk$)_I?ki}m}*f|VwhMCYk9{`)i^nq>E8Xw{N{L;o{(A$kNmxukuzB9)|e-^8KFn{ zs2l%Bu8Q@+;TPcWT=(36SFK0NBlNGkZv&z-BV2C)!dFqY!VnhvA2Hchl3BZF2{ZEL zlLI@R_p$O9m<>cehnzY$t~5`7Iw1PRL0!Q#BL_`POngz4GVrpbia)m6A%EDX|7RqZ zBuiC~^nFkukdB$a_N3H+ZZ|%25Zof29QXc*?L->J#`R4*-+lo4p#l{ArQ!w#7-;jD z{!oV-^q*3lfy=+a*&M!l?dHzk#?O?<^sXrVa@*daNm#xQwK>#{ZV$XkNy7f6H9pl9 z@MO9xSTw!D4!RkZ4XP7A;`$e)D}j`D8K4AERRd{$XzC~%*I*ThRKQ^oG3Gb+BBX<1 zov~Oy2cB18#Xv3THfC`;12)1b?(R2PI;lwZ<&BLEpKfxFtmB?bKMV>Zwz!U~2WZ3JqAVHI};g({p zhy*H|NuhA0`xw$OyF_Ot(Bl^RnjYEtyl9Wt-!QZUjNxr%znXqAwllh@srdxYAb95+ z{`vMk9Ua7UN==dl{a41!dJI^E$TeZKsec_}fzzFz)F;#LWep@D>>cEs7((|?$-<6* zQ|m6bHxPwwX-R%9e~y=JFrU?Zf#fiekQv-P1I2KkLnYZEhX%LMhi_4P$TJwIO{jYBsd0pM#`NYx&slAJ4V1}U(20Vj2nPV z83Cs#WJc|wlATK}mOPc$Z!_UreD{N?Q?w$7C_a1Y_=4H(pHG1@=`EYgFOjIrxnuB& z)y2VCmstT5yTQqJE5!f;$CBC;8Va+iBcxQ}Lg_KPc@}#DAB_00;@5;MP#bHI2xNV| z+p$)|T3TU1vQ}r~-Ft`6+XJXRZ@7jvFx18_cP`qzXIWGuf3ny<@s$Oc z+yq2NF;*mx17zC{T0tX9{nwqxdvgq<+xzA10R?0GgW|ar=IR?s$>Fgon{GsMdy5z! zUPdqXm;nzmV)jBV$uoi_RZyyj7NXylVlu1f0Nq!%v*F>3M37eZf)SA(1aB4p$x&Kb z+Gb`*S8gWm>}-gN3zh4oaYr)YJW!IbtFe+g-WKg_JfT1+hNEq?)RUokofZM()B7m- zO9C^h$xhjnXCAQcrhP}~pYW6wx?U)#s05XWFs(SN+You^1Frq;3XtQNxxnWC4FsP9 z<&N}Uu7F{ESF^SteA=Vel6a-@xYxX(tm8OcTL(+OXm<-kl8>6sl(l9_>X+z?XXWob zJ}=j~A)5BE;7%?mHoRomFVf#02Tiov=Wg!$5Ww8xx-pZaK$H;%sC&&%D5A%HO`Hf9 zqb)C$3-wAM=>m?F5tU?$1brRRyNfNZpPxY1U$jRG6HNo&_$_7ZNcyh&YjKot8>x@)z~4xMPNir< z9k8GWZMeatH@9zyv6nHdXn1&d411o$BWg|JsA~z&ksYsGh`-Mh@4rhu8{~g)4;WgJ zDaiatPFPq_X1~>Luu_J2(icu2A3py;#f{I_X!y+3OQ*YzUffzD1cyF(ML1TcW@zrO z+vp;8w@s)$i66t};csCM%op_!dJn?I8;G(qdXQi%=kE zGS5yuSdgn+BWS;x;a^|8TaYqXa26K*+t*}}->3=bn+(PD$ZMfSNOeXlK#jZJUs~Y% zvo~EL(2Y+-MYV#=IMD*nnMyKikl)<>Oa(O4_MZUi+67t?LHAD@F8n6IsJ=mkQhHWF zWV5u5EKdVd9WQ&X-R8q9j4T6{kzVv6@gJQ4GEP@L7Ebgs(O@7SAX+~l=$qSG2`nI3P!zg**kX9~ zzIss%RNa~xEr--itREZ$PBd9&d1pVwu$i?=)hK8G(3yU5>osjA5BfoQp^6~>OKGI2 z+JzA<39Olx{{`wPC1;E+D_HgPHhhN4apWvK#XxpdtvqZp?djPS)=CWS0HvsRzwcZ+u`SS(7qkAFOXKuFuJ(MMZUm@j_7_0osiK+MTIRpa9N z;7_c>3f;aiC7R2z2caETT%B0bLN3tsm!}D-N>%htp$b84pHXbv!@25~A(NvucO%D9 zm&ujx-S*@T3wW&M<2rn6mEXLsW}N*4*y-u|i*GHFHk|kv8H-1&KqMDHCEdvYaL`V^ z4+GZq>0X|cQNW=5tU{fG1{9p279V)cp!OJ=T!3`jE12X(2|ir|)XNj@;?Ey<*KS)L za}9#urC>uF!e7T1TEEqJ^h@^HR3=8NI2|3^C;fyS?%PFMs~$ zbt@!9exv-#Rf$_6a#(gdf4u*z{5RZ=ChnSm(8k*1dR|Obzav3Xf2mB zRfd3=+oW6mG2?4QnBa|kPH5oZ!ig|nIr~4hWf5A)Dk4W|j-d@L`*~U^a@T`Qlkk)4;+y_s*oxd#wh8!ohTlhBnFpA*r? zqTqP(-`;RxDg0RLT37E(=`#@Qu>-Eck8nf?r`XHGOsD?a9*}N`MoL0M4;5(%GrI2% z?%1=zyCGkNX29ZI7GAb*;+g}|%P8n+8lc2&+xi=zFYk*mOB5|hZu=1f9=8)Ig@usm z@-eh)h=jzj`b-I|l)}CifPxmqinTkD*SGMB79p|9S^_n)1DIB^-@Cp8`bu1PnvWF_ zL60&?-Hnb_x(jqnMrI(elIqanWO;fuVD4DaE3fK`3u3F2gBj}Gi3Mh>=8l)K{{nA- zom_gu8v?k-w$8Rd%uz-z$vYqoXBcvq3ou@_A63@dyGkt@LV0OUVi<8MuxA(QsCxIr zRG*tpEX|xlQi`!iiEZim4z9)3a4yOTAjqDd-1uXC5Zwe)+;L4VMdv0VL5Jpawb|`F zqlZ7Kl`KeQCxlSL_sqHbJQe`Y>5qtPXABVfIeJ474_{5Ovr^sz7I6F0V za>bXOX?1Sa+@z3#&O8$ei?x&hBuT1Z<5KX9ckml9H!u01^$lL;%t0PT0bEN)v-(Tx z+oxG1D!uiEIYKR7W(+Dg(xrA*DtUK5j1%p>8zrCM%6`fR1n@h_?QVHIXUZoIC}gmo z;1>H6GDD>8L~hRo5Tn={YQMd9ce6?ttAj(xD-F=x>u$C#Qq5=v-d42@nwyRv5Q{BY z8JUhB<8z$4)h1#yYE)VP180>DTYO{<(rzh^v8N_Ps}0n-N<8Pn_hb$+Xc=DhsMJ@} z&+iiro321b*OlyS#K_x2CHnapun?O3&pN`0?Y%=^G>iAmtI+&3Zn` zDN#K#_4#1*7EbO42M2|HzmfHd{O0NHP(rps8O0|o%BOxMN$AoftC$>1YLT_{QkmK- zEy)+}Aj}-tY@`v(9sI>|w>$#=0e~5?;v8Ip>K94xjsPTzxISwXI&q;b0SZx=9*CZ)&{JLCqSpPiNv5t7&5Q~Plm3?+PMEsi^y>a0J%mF|8Uff!4_0!VVs zFmS^k8G#_dz(uw@JRHz7`!Pyb2|;<($7D&NM*JX2XCiBLTaL=_lEzrVRrSvk+W3L6 zuLe;HsP)4Mr4I`Ah9XVkByp&OY*o-_H1-czed%>81lT5jXqTR;W3J}^K4+j>0S&W; z?oGYBYoDB)q_FTZ5yD*>iUDQ{vj+6Nbu=XaG^d+sE5LxCXv zzz1Z4BZGEHf&wHU@3^N%poN9hyb00N`H7BZrCsL~FrR3Xg!h-jba9)~RBE{Z_jO0c zB;_Z5jBN1Cm3z<|;^z7-RVvS&O*4!1{H^6qo(802TP;lP0L1OSFKfBV9MbL<7I|zK zBK4=5n*fTcT8wcR=Z1)C=iIZly@f(s-v1pws0<{a2Q+m1h0Pmv8zcfDGTf|IF~Hc? z90X_{u)tB!7adB^^l*u9v$i86D#Vfi@Q z-U+neSmGwA{Wgx!3a}^Pvvbi5UU)qBSsjp5M%DKP15$qv0DE`jNuD70?p?RHD~2$! z{sOz6h8HkY_MnnEsQ0W07jbNBr8ltj?~5w0fjaQ&&KlACG1M{5|AbdQE`wC>hX4?b z469u4M%%{bw@)^=tvh%`8(QnwLBzc@1dJ7ZBu z`j4~b^PunIMwO`mvrgFG#O0v%>n0v(6F~U$voq--3t?n4fl2mQCMf=Ol{IRx-ZjT2 zn%p2_{qnYknpcZ-fGJw=!rO;sNNC;~#EUwK!QmeHCqZU~$R zV0~4BZ?`7dBb`V)B4mMSGLE=M>#%}8dyHH_FT%aANcb&qB!{Hy)@nvbvxJ5nph7F# zq}7n~EpDMRiy-!gLcPwCO0}SK2iKbgh`n;kzXc7aF)uTE`}AX}6sOr`G5P})%n=xD z&w)>)0q;}!|9zj}f~x+PAPj1v_GY;*g*84F*mD^<5<2#SuO=49>;*~%H?~KhORtFi zaSGhsz8hX$M%*lPrvIk0%DMmI6cYYx*Qq{5#%w#b^?5qW)JwH23vdNi4~1K1{~0C*CQ`=2vCV&&07B3*pYk`t*xHEFpYV z_lPU+-)WO?P}V>tJ2FR?*j?yWV1LMof(I)@kp0u*1`1p3q{1-vut#SU^ahNfNr))B z@CiyBPMzBZl$T=KEJ^liB`R6-)u&guB8wLIO<7}rQ7^ndh%+Ue-;K{wC7(dcpw)Sl z0p4wG6zaC}O8c5UtlaA_k%o&F( zbC5kw@MBh!!%D*vWStQ9{;gsYIyGZmjR*SuO9RlC8a4`XFE7k^?~LxRX-kkiM{sHX zb6`*zZWu*IM!xThamcF*Gt6ItlvP1X(cyoU;RP@V#p0Ea4J7zFtS9rK`v~y46PIuf zb2~e`xY=3V%s&x*aW?;-03`w1{)~-{wWNuSjYYOwd1GVmoFnlzuh`gF%lsN$Y^*g+ zY-}vZ>@v^@$!!`N8*AB~v9Ylrv)c-dVA@tOF)>}C0`;j2iHV8Rsk+Gu!V~TST?F9% z6?kxaGsefq_lb{>$7!GBYn*r-yt2`1m{LNW9G_K0e+uzeX1yZ%q>) z9}hCS3^YP=o5sh-TefF>d_2hPwn8JAwiRU`DzJ#UK%HQ`tsuOxqul~_3%Iwyzgt1v z+rqxGTfn0QYz6UX5<9_efjcc=E6AO;u+Quk@Mr;BK|GqoPOw|xP7Bxya;GiqGrI*m zTEJEik0uFBg3aZh(E|Sm00960s+&c500006Nkl$rtAsu=~kdST^L>L7M7oDgk&+PU4(Ve6Qr6pC4a*f^bE%4?kn(Uwg4;8IJp8KoJvxkjIpfc4>I$ zJZoRxcJiU&?P1~UVxWOXX;Wist5ZYchb8Uz<&JDGnGY368P@Wlv0K+YZ zBx0OUHfU&x*GJ6ntRk_dC}0cT-rg^SG9711)bndwTU*_r79uPxEchraiy!MI&d$!& zqt^JBmzN$1^sic*Iy)c0h!Sool*)U4TuOPGprhp$_oKbZd}qs8Z3u!(8XO0_5nwAu zq$~{S@G;PolJaIQ27(7iwvr&u*5m|~?QLSU&0=3493LC)yS02^>51PYqim2+L^E$% z8pp##_ulyaK!%*K0_XB(9TTkIK;eJZf+e~|ZN_LDs;jH(Dl3n9c26UJal8vo2K66a zwq~j9h9)r?g+X3Ab`K}o@?-G4^f0Qnj;;Kf+)1tPO?q!M`M}d4p@GI{){C6>JY6-4 zh4^5=1%wD4ieN}%Iq)m)kqdEc?W^Ko6-1aot;7^4&u&pmA!!zd98=hgHk+Aosd-&s zx3~sDJj$>c(Y@G$R&o#P&!8)yMHbnmy-E7RhuAwZGnH!wojpZ<;pF5rGB7gomk+Xn z#=Z^FQWB9MmudN=am%zk##}-i175Np){X&Hn?iVZQEOM5{ZFC4Ky%sVr|AOr3(dq7j%XW&7( z#BucZQp$rRp5k1TnYsOn^z>?CL{?>sxaO^{t`hB%!zt@Qo1NrhHU5zq##1qDf2p zcvH|eFMC@*h(-cxQ3pZPbZq`)Hq1Sd1bdW}wc=S+Si}mV^jJ_{1QrD5(zMVmbmW9C z7*0llD3)8^_V&ffr2BZ8QM4k(cHg!TE`7iKHfYXLHuhTsI5&)T2FFy4fdD(_ITx1` z5q4-zweZENse6lIF~bWtCY#^CMq7v?ww@vFTC{z6A~PdHOepi%1PqTMK|p-@GPa&SQ8gikA-Tp! zM^~6PH8y!o+zwsPfQ=z0MFd!Dc({=MV@5&E1=hQ~@hf<2gariTHz@XV<%1}t*^3aS zf_9T0{v>cqx##4NH34fhW01ZoIG8hzYJq#CHmf3?KwH}iF;ur9oQV%XxCCsN$3O{P z(ab(YD>pg*;>ORZZRJ4~ zoVx@;3`W9}IR4u-Wj247?`sq}G3IE~peSivbP1&apS{-3WJoPv7-KkvObe#PKPd!Q z`c<#~v(HmekXE0+M+*kV{+B=dYB^|H(&dW++B>oC(NFiJ9rC^RuVqAw(0cdpEr?Oj zr-VICY&#liYCMytSK~m8k+-~5NmZ#RF5c}f&Iwo?hSE@yu~PPdv^81DLjpW3V%6>K z4;Np!{}M~&+7j0@QNoZ;-BD0hR;~#F$*8t##PDL5<N?&MuB(eB+T>ott zHySt}xP*j+F1Tpyimk5@Mdb_TQ|#n;beDJcomEv;s;M0M7ZN;BB&W^rfPe%Z9v*E# z7Xt{r-|2k#4Rp(3vofo~CnY6C6AT~0g{QfB{Z>d_>^l(cPFh%4Xt|9OUiC7+x!B2U za$Z}-FsRe==oOI2QYoI9pD+1*)LjFT4`cvBD3W8ulM(qauuUAQP^aJc@(_5ThyTzu z_TKrM>D<}Eru|C!Nl-;M)FkscWB-j&W*%#ky5gw}_@l@Y08x4vE zJ8D=~quvq^ia%n@;WMy{k#i=2_wN+8?w*F^0d39IEYR&jPyE)Ya5L{az|awfKd<()U}OUk`m=!7&4* zKMj{V0Do2Gt)-SXA_@0!RoZWQa~U(YarQ`{`{!J6=i_4HI>6IcMU@ZPZ%N$HIbx zpeJ}^V6b%R=I#z3;xaRE;sOPiE1{sY>C7}IE9+RkLXL|}>5*>VH5;^@$s;zyR zWOc))8Anh(e71G*201Yb&gCaTEb*dtpIzwk8jZRGVZarz{ZVeUX2Buc>Fz570AaeG zb8xittNU-_LSXSry^`vcB_%^09*48YRTYnRB!wE99mevLVSxrTGdL1XG%>@f=I|#*CUCMEr5XgN~jCNmBWJj!!gt-o7C=^?6 z_Fs1q`BjWh6A}DSXiuUixeDyQL@bFm2$sau64GOeSRHG|V1j_N?33nc6RzcvT+V!Dkccrsm!xFPw0wTb)16xZF4GV>R%W8ZG51EeOOMgBiV~vls|~XUMe@N#mE5Q}bSM1Ot;T+D&}$y@l_JQ`7Y6 zmDJ{EX+f!@eP+m>EkFeyC4C(rCSi16hF$80u9}^4&51E$Aen> zAta!64_-hQA$;&`M-yYIC*(L!Oj40Qq8x$-NtkC`#oYoyl;3JMT~zA6JGL`6hu5wH zJf_4njAqZ#GkoHs-cYE8D^V03tr{ag=s##3-CvdvH?A@`HzjZ!k*sm%v%^+X+4_{0 ziy$Ghy&LWJRXDGTEsR)|u|VJ8Ga7`a6@YILMAeo6sD1pY3oL?vkn~f5XQ;<1bL{y@ z)@`=ks3nm^Sy|awrm*v>ud87ikr3OuqnS9$i!p)WVK&qd$sQxN zC}kWDFbn>H27dqI-;}or6qewY0rK@1Uu&_lva(*rThHDi2E$<^Lhc5FT4{VC?}Mkk zh-Q_n(F(qOQwN}BTzf#NGV>i~!WJnK(jSr;XF%oPvLSeX^_f&R2bO3_De?6DVBM!T zhMcHf*e+e6kaHHzJoZ%xq%~$b^|XH+L*yhA$UoDfACC6owT0EirJD369A(sYz0MAC5Ujs{V{1mc8#sc-Pi;`0M#$*QUM=FJrF@SvovMEIv{g zD+Jtp^g)zKV&ifZZ)HePAjZ9*+am!K9<9)w%!>)(4^uM@lw^}+}JW^P)#&j8`*PFr(G zz?iPyDI`5aY(1YUIP@WbZ!-nVM&jf44`B?FJEN(tcmRNZgfH$(WD^|6j@uoLR9|MD z8Fy9IOMw#wy>Ry1Fn(CHueLIB9~A^i*C7`}_rYr|q!tA1nqpkXLfYD|a@A}@My+Ya zp@$>G!;4Sw!a|ZYBJ-&l?e;_nZDlbq$DZDNXrr6Dz3_?Ht0*Z+Z#C<~PB$C`uyZk6 zp{EOVP4FlPw%Q8~7Gdzx7i{f_2h+MF8M1TM+345qcZN`|tr(3VW1^$M@He=YEvDjZq2E892QSn+VssVSQM+I~a>FqV=Lp@~ zs3(PM-~I!tgRMe6zZ_HJcH2hflajTM|(vOdbVlos1#UIEoIU~htZuXw)c>_<5vuoho(eUT9%@=Ri{zwvQ zG9H<1*_ymRmzO`Q-!>eiT8*U2j7F!r1tq7Z#{a(YTPJdUKJKG6(yz1rd3kyHE{|yn zbM9uBQMXAf@SG11mhkHDd|um0tNlPE31fGZfc|fiq#*jYb=IS`12IqfUUtiub3k0`hhL_;&~yt`M@VmUve3JTcoXIQh{qm5gTqaXK;W)K#aDw_GM;L zQqt+XL!SoE@}U`?;zVrzV=c)G1jlZGYX=olox-*@J=_hKGl@flIY_!FZ6V&!?;+3#q%gx3BJK^csk!P=DoqcW9sSx1C9$ z=@}`V&g)Pu9i4}Yii#t&P;sw8T0&xavFkUA`;0c(f?CPl=ZsT7f2MQ*aFD@A>oqNVUKUhGqP z`aX(HcH4alB<}|#uTbmSH=c|rA79_5Z1=GjJHCI9UO+34y1Nn>UI!di8MQqhjH9s; z=ogh{>rk7n->L-{a7x6Fn#*{{XtZ)r_CZ52;gVBAqbf*5L z#9tT^_NE3tDRAtnxc@2~FGp=Jlvw=wJeG?8A*ZJ`_xJegz>Nro0Pxb?9}~%^DnT{6 zVe4??sr{^aaKAbZ;kW{}%c z^o)!Q>+iKkThJz=ocSFWk+FodWlnBxXFqp$e$cjnaU@+H-5ZnN(G>mLM^O$|2PB9V z=1gk64k0K3mzxOf-KHad6no^nc@%}^p4~9??p^wx57uDX0lo6HwVGnE5wBBJb9y&C z`wgl>ll3X+DTwd-j~e;T;YI}0&<}F^fD8C;W|5tzrXpNrIazwu+B8ORRZD7;J%%$C zH1xfW$;}?ifc=X=E@a=+QLN{drzrM<}y(GjCs6`az7llKD12i#iSMdviJG4V=SrVF z{5@o9)I*2m7<_GQ_1OgUS$N<^L9@-#ImH!DXpcTo1O|AY^eI4TZv+JeCpz^*nsfh< zu>%wDQmScRK0|)OJV4 zkoa#0gZ2WN`l6~sZ(!c#QSU;@Yo;R`tWqq3K~JdGWr2CH#JVA%G0`JfNqB#n$2`-4 zU>4Ymk?qq~Ra6)n8XFTm7$|(?ky=aW>k*MPDz%s_t<1{{ahk8mWlvS?E3D9yB#2Ee zD@)A%`}zfzR8O>eJnyRQ`t)*ia$1^=Z-+5>Z!_hw!$kP(Nj}u#+W_9Qj&+_ecYhYf zBLz48?62izL)AQ|N|msZI2vKfl;q_0(HjqcXMg_=jxk`1#J50shqq!8ed+`I#qBH* zaI$>TAMxV9=1$fC?ff)bX@CQ8M!))gDkjZw7)cQ;#&baI0n)ow zw}$iX*CFF(IGI5OfuW>;vaezi(^<sQmfwNo7GH}V|R>{8oi%F`$PP=k|LWaI#eLD(gk$vmQ5jAXu31|;kWpt*~TX5r8 z-icqtG-xWErhL^^RgFu>fA_(;EiQ)x(9HLH6S=%}baagvI7CNvOUJu8t|+SZe^cRc z{wjsG%pgp@ZuIQ@&oe{!LPr%_d?;+?~+t0B+Nb`#~wWOw@Ej;@nhs#$C-&&Da{b!Iwlk?;x|$4~bMp?-77 zPc(FK-Elb53nI=?yL4RN(n3`w$q9#W{5tpI$^z(hg&c$bA4z#jySfFhfdRc<*;H&m zr^g4*=xE=r;@ssfF8t9UN}XW>H)q`Q9ylN-wF?eWuzmW!ziI{kQ($Dkymv0xp?+V> z%HG}nyZMdL7to(@8sxY$oU*20Yl$o3gh$nno{AN@;`Evw0tQWH=s(4dWH*WC4HyjJ zXT3XaTI=-KpDG-GTjOllYN-$C=`e1LWg)7u3x1>wr{`bjgz8e;X{YbV=0HqKZMDni z&vsLhDQM{QZAH^1b{T(J6!)8^EZdvQH!gjgIk>qW>8`>#r@lTz%VQnBNuKI*hvuIE zrRK%&-SOQ?N*s@7;g9#fGM!g_1}JnRIcFh74NQP=UM}L*5b`~MRHZ*XvPS2G|J4|F z-D&&pv4u24x9?9gM^Pz#>?;1GZ!3$7S>7*LrLEZ4EfDbc>Jh<6W{KBp1T#Cos%mSe zag0o)YGaLOz2Nr+*2Gw`*b{v3bG;GaOJ%{yTVz_NgHG4AcFM}i3Th;+ySjajf*Plg zjcFv|e&RQ}O+POn+)%19A+`y*uUkM1UVpB*Z)CkhaydN$I8(7b!PM8KKZa}ob*8_p zAIqA6fBDc_KmsTjVznGnT&{H&mLkk|PIYmSxBE81vDlG$tX+A@51HT1_P@Li>?|)U z8-C~XOnp>CQnZ!)g7^PwmRi=H2@Ni z?fwoHjnhoE0`_nR;bhj#_W>))vN()=!PWX#WszM<@{}9DBSBrOp7u`$2qI{2!vUEH zi>KiUO4XM%b&J%uCM-9GkEeQk~b*DiHX*KXcgQ9M%nj|q+`(h(j#`NVsQ6AX!b z6|tRa-E4P9O+#aqHexk!Xk-BRdSB)av5Yr+S@)iRHZT+EL_hqq9ZqI@EqrZHlL4rH zc~@(Jfh*k?V(GH5lnf;>+B*jRi_c5PMLKP(ahB))bZ~(+)Au_YfL6XZHB~zp%RAlR zL|W}#2Vj7UL;X5ysvTbN_gcfevV&jJplv=x7N-YN&`^uwSfJiFfK*gK&*2c#Z z%>iH^mncVv?CT@?dY?xlT%CMJp?LZRLn^F`!;+ni?M4y6r-h@|{EGL~dC4ks0)!W; z0dPYdh>UutDw{taN+nCLo!)}h@y_gm?0c!Pfq|&`zw7hY%*bWo_1oFA!K>4?$H|BL zJ6}RwC^Hrs9S}W>&V4ho07*@#I;`;*=EzlJ&4g$qrlerKua*s?ASa(FDk|FQ9KeDB zh*c8IyW?GMx0snc#UA&US$V{Y;>nZU*_jzo%gT|!PxTVPNUX)BrPnR%A^6rQ%5DkuQy*DQur<_Nw@`8JfLZuO7CicLpiLG}fGj7r2R&CR zf~Z5}!_m9}PI(_b?34@JM^z??2U&0_4?noIiklI6HY{N!n*u-d=~z`)DgI3a5cSJU`S4E= zc9y2e@bK^))h}Y+L((fYTK}=6HL(Z=0Yi`z2DHE1SQL#W zt0eLL{!GddkEio)B|njB2VFdcZ(E1@5WZEKyA;KRTW8aamhjjbDNm;=CSjgjNmwM4 z{1ID@OAjZUot<6v*a&;ldvl)bXW0&_&4RckP>B|h#V8mXucrU{^~=3i|C@1ab7cB{ zMoFeD<=)B3>VIq}9}8dWBeysRgq`_cF92nGGa&wcu`N4CAJWBzvlxApxx2;fO zp3c$opEZb3+;fRxuCZEwrQ71nI{8ToeB67umLoS z3eJmin(9iaj3VV-kU;)ggx(n-SuZO}&KjOA@n)9w>^0%A&lLmk{?};c)mvr^>7;_` z=j`lrRyH;?Znl^P{*QNSkN-JJ`ZRc9@!!0|5xM!fY9(aX@xDGjtw6974~X)#gV5!} zX;|wzwb(Cy|8{n;vwK169FaFOasikzvsFe00MlPL+hZmoFBQ{TskB>kW-Uy}x_&d1 zf4(s*3s@&h|2a`{d!x`pbQUQY^>v(#wlhp{BJr8mU`dLVH@#}pRfGN9L*MrIPt_?* zxXVQ>=~l)YB6u~#0RfYFGFKJ)b-#E9KSa1|0u30go`C5>Sa^aFBw@X7$}^u8sp5_S zmYaH)F3<&Z4s<0m;ih+6P@3XX@tXG!9UL66B{6a0YVP}uo7QOjSmRSS>nbY~97h>NA<{HF4-vPZ#YtsRN!q*#tb8}+Xg!%YD^luBe%Iu zNVi8?pvy6OGUahEi5QC3dv2wtKZ1_wr4>A8Z4pjb^6>=FlqF}LITTBGe$q7f(FzI< z;70|uV2Bxo!7y=7jRLv?KQ0~9&lqSy_nGkZ=wVo3xR=0>_g#gLnWCO2oPeYmaws=4 zkj4U;pT(B%Prx0+vlgi(wS7bi5mvYOYH4>^G-Cxmn|c0C(8)01Z-9IH z8)3}Nl!Q%PeNByvFW_ZK{vNe*z@NpD-{wHCI<)?@UvS3}ESwn%a$;Jao<%;7_6I@FF#8nqSXtQ zfK|!u*siVrO%k7v0D2+Vk`DWxyDqt)f+)708O3|EYD~t|3F-ZFQk1d@_U00~CtM9z)g}CCr zJ#=f!oM=i(_f4t$dUx=p#b7+v(?s|#t3`$L-QQ!?9N&|cZ*EuAjoN%5Hzg_*6qCu3 zmx|xK*@79rcVRy)4FAc4@pSz?V3A%>RVyvV_+Bp5*(?E0&|SV<0ybCB7G_An`XC_r zobwLhIWVs>W^5{8fLUu@w`5fn6uysub6-r7zv)>E{-g~6epSGi@G}g0ipA*C%VOfS z5$0?Zcz>CYK>OlC8b@z=a`GIA2rgwN5sB;qbf&35zyeNFQ)Jjp-*BrX|DmBH)r!tG;%wFd~ZpBp3p!9*?=dM<5j{Kcy+(z=1*!FL!m zfWAsfz+gnx&zGgsjs zyx8-fNQit9#t~(r16EZe9L69|aqAIY30Zy~d?zA;(fb-C1ypWPpd8`o>+7@A(9o<^ zmy`rOnncz}{GgO)56b%5F}ii0_2JK+ItW$L*(q+IqB6!ii-&?@zfA+SJ-oKA?yj}1 z&DY({Egnr;`)5E+zXO4~q~5wTegVHkzb!6)!7>0s*%;7sC0-f;eO1|Va(k(6K%-QF z9~g5WIpqpu=j)`#sW$tqEw&8jg@)O&kMytrT`ANm>j4 dUnaYL^qLPCYA(?H4D_#o6l7GSs}QCk{{z&%)G`17 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_device.png b/frogpilot/assets/toggle_icons/icon_device.png new file mode 100644 index 0000000000000000000000000000000000000000..2275347c45db570b7b330c1ac80f35ca34089ff1 GIT binary patch literal 10538 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 XD2PzJZUk`n5uv86tyHdXKji-a%2FE( literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_display.png b/frogpilot/assets/toggle_icons/icon_display.png new file mode 100644 index 0000000000000000000000000000000000000000..d6e6c1dfac3f01955edf00e894090085407e5ca7 GIT binary patch literal 18009 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 dNklY=@;QhwyppG002ovPDHLkV1k!uk4XRk literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_frog.png b/frogpilot/assets/toggle_icons/icon_frog.png new file mode 100644 index 0000000000000000000000000000000000000000..3561616fb26f8492c43ac4a0f6065a6efcb3f073 GIT binary patch literal 44342 zcmc#)RZ|>HlpWlMf#5ng1P$&C?rwuifZ*2djlH?cw z00U$tL^V7O&pMHEEWT;7bhtmC9M64=mt{|o&E`~-k)g=Vhn14RQW1@SH584Y=Q4~4 zGu?k#b1saCGm>c0mW+UuP{bP2q9iD=|K2>BuRg!|H}(1Q$D_xjf<=fzJcWy~^W@Uw zW7nyUjZk?f4Cy;x8?3apaW*4}#2`JN(a{y*p%Ydb##w_ux~k`g);2vPI$~%BmlriQ zpZ&j0pdCWAZmhUobAGjVd_|9{zo0Dl%ikrzhW|bB|G7NH4*}l29w^7Z3olnc%bY%P zblb-?0Fc^qVvcUF4c;(r9o8NjhK}0aIl5PnYWa}r3_aUFxK6hM1D}TJ4%Pj%0DW6~ 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

CT=pSEW#EBIF8hO^6Bj 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 Lts+$?VHW&9M)5iR literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_green_light.png b/frogpilot/assets/toggle_icons/icon_green_light.png new file mode 100644 index 0000000000000000000000000000000000000000..b0c06f821342878c7af17d919d89bce75284759e GIT binary patch literal 6780 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 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_lane.png b/frogpilot/assets/toggle_icons/icon_lane.png new file mode 100644 index 0000000000000000000000000000000000000000..066d1be59dda59b0496a68955de546a9646d1156 GIT binary patch literal 18874 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%0Abl6(Yk=X=8wF`@qiTT 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 S28(J4L|s{1sY1az^#1^kq{Np1 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_light.png b/frogpilot/assets/toggle_icons/icon_light.png new file mode 100644 index 0000000000000000000000000000000000000000..3467a116356007a45650803a2784b80dc0da2310 GIT binary patch literal 14116 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 X6qL@RFX%o87MR0v=VKf@zia;haJnc+ literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_map.png b/frogpilot/assets/toggle_icons/icon_map.png new file mode 100644 index 0000000000000000000000000000000000000000..e952e5f630dcaa610af473b7108766b11b2a6c45 GIT binary patch literal 20481 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 A=l}o! literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_message.png b/frogpilot/assets/toggle_icons/icon_message.png new file mode 100644 index 0000000000000000000000000000000000000000..ec7097cf45fa5cf6ea13ad90e9679fa56f99d5af GIT binary patch literal 7068 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

6NS= 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 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_personality.png b/frogpilot/assets/toggle_icons/icon_personality.png new file mode 100644 index 0000000000000000000000000000000000000000..d9fe2b777628b9e29cbbfd7d3f19423ca048fe3b GIT binary patch literal 18906 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 MKksPs>YP{NKVRPr^#A|> literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_rainbow.png b/frogpilot/assets/toggle_icons/icon_rainbow.png new file mode 100644 index 0000000000000000000000000000000000000000..3b0379c2396eb2b4360fa4b44f38cb863b4220ab GIT binary patch literal 47107 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 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_random_themes.png b/frogpilot/assets/toggle_icons/icon_random_themes.png new file mode 100644 index 0000000000000000000000000000000000000000..e994abf0ffa3ce5731a37929a3ae5fcf119bca3d GIT binary patch literal 16731 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 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_road.png b/frogpilot/assets/toggle_icons/icon_road.png new file mode 100644 index 0000000000000000000000000000000000000000..e1e44fd879a121a561fdf1b32c482f44ed08827f GIT binary patch literal 21042 zcmXtAWmJ@F*B*xMlJ4%5lEdXmg-s=f zx@oiDY}d3c;vD7dV#($AO0Dg9y-Q#N#HF(>Ow7Ufub@tuLG|YTY`JlQpv~~3(&36} z>!Q`upkAxVtFx1h9=X0Cvg69yV>dKz2!0pHP7tbq!>tL|>S(^7rD=i%b_^}^N_8+xl|8Wjlq=a#@qOrz%`J9FG1 zU5Q`kYTLVjF>HgG2YgrLMoibHuD)N2&JCRT126CB$}=7~Eq-W(63v>il==8Cw!OPN z+S_lyT0{Gq@vETXHW2n+>OHvfB~JYc6Qt!e@7Pbhgb~6hpSkoasHsGqmlH{=_5xg8 zPp&Ul-yPrUL7@Ms^L}eAq?Pu&;`cczAGW{Rk{$ZYq7k0_b9kvD_^0ICMc13s5MjP+ zW|Oa~@xevUGoO{uR5oL6JX~$XK79ec_I%jBT|J4DBmh~@Mu-}##g-`uU-~Mi`}==$ z&UW78!e`3#a01om<|D~GOWuE0b>6 zg6q<{iu~-&p9ul&EXmM|zzRgN=T(=0Z7@z;vdM5-E{e(&f4^nTSM?l(>6}woV~|2$oTy4tNO1p zy-Oy)MpK_2p*hT!8IFWiUCBu`SD1GN`wT==i6(Q$pXGEN>E*4Sb0F>MHJi)AOF1AE z@+YS3c{Q?nE%*2L=zXztYoBxCh4B=;j%DvgIBdB_X14OoQI<1hiFnQ0{V4F?k;2we zua04*@bcm5Z!g&r9&lVd?EEzjmKu&Bj)uHc@~2ET#tqIZg)hMEjc3aQ=*I6f>EH(1 zfQJ=;TUBCCD@*nFZw?*KkH0UB*TL}`z(URy`>8Zvo%+;vjNPEh{G-S3u}oo`6M9B} zl;t$ps(ak?(^H?h>Hi440&BpJQQ=+C6dD~mXtU@EJFi5xP*qr8@A?qFiuC?!Bm7t` zvW~*wNQ$`1@;5E=`}gmqD1{xSQ7rDHFE4zzqD6@imvq$7SsJ*mXR6moBjI^4(dy%2 zHbu7~#Y?H&>4f>!VWuQW0#YL24T&@DW9J)mARs4i+ztpV$zUzzS4&a!mM$3n+uA4} zibe9O&Ti7uApff5*%7p^X|W1iQNqKMa0eu_L*7j^!{>9`x83(65nERKX^wAh!f_YHoE$WjL$TYU}Eb#f8Ow#Q=qoY z{XY*^Jlpl&kHj6)o(J;+GRQT?%yIPdOnnpu?-w5fJ>xTaRO z_O(ClN2`?LiUE6zJ>I^^i+IvGGd^(3lLG@q|7J?{`F2nE81lzQF#foD$|)udfPdPZ zE`gPbFpHTSs$Mp-QzLmX5x6*#_Q2$M1+Oz-|4927coXX^X_^MAjhfbR=fI!B`*Ou= z%|mxpHL$AnN=sHhz<|KwnUaAb+11T{Wq3DcT0fz{uH5YEv-?OBV~Ork5=>xer(7|2)b?d z+TRckC-WH3A|a#wo7<}6AOG8vCj1|~!~1m82lw|MPDdlqOni&aB%$k9X_?|4{d#0v ziZdxlR^s-Rj&uT>lST!B#fc)BIXO8W2#hfmlg+KfwIzRndcg<@XY)su6@F4i`+of# z?tF|%%<31uuns%%e6H8TV^nvdq)@lU+a>qatmC~u?5z}P)$NSR+g1Y@byvq{Ec_*s z!r_^sj(<}cd8i`ag0dmmerIaw`*W3#730U419upWxIyNSTh;Tp$UDeL^>Z0LgWONX z)$o^vnyIY>6TiKlkRDE}1ajh+tXA8N!i%Rm`xH!k5=t|Ph5s#n|4(#@L4oOu{DY;O zAm$3A2bfk)67}6KHhJ_dOLu&AJE5LOXDZ%*qeNcb6JwO-cP-)kXVT|`PboB*y>P|y z@sAYrbZ1=pwv0a;a(he95H z2O*;;a-|m+#~Zf?_C4#c8i;YyN)x(PJrID4UaaTaqAUl?PRO^Vin^Ho+|Rp2!g2ld zPoWFvmb7#L-8X|YS>F{?rnBXs>giRn;NwXT{@Pl*|7{E>&y!1>+u`yF*Ll><2yHX0 zP=*6!21EzLQ7K@iABt4{D>xiFRSS-FH0nJg=K;xg(8}Vc{{R& z_l`?4dmf7mIZPMlN-Flp-=G?RHYUilYIq0Qiv^k_Pi2)Fm}XfeedQJ81WB`p;C}G( zqU=MFf4@DLuV%9Jd!xwwS-Z82b0&~QBbhrtlPoS2s*5u%yOUusB_+M5(#eB?V$o1j zQi@H=ZD7|cYCrY0nvm_(0-`^XnkrO~4mx5P81!LI#+XR$*T2#QevgUkfjsk@j?YNwU@bYVp8G3&R4E)bsAqD{UY1gZgZYRi#I=OFr}sVH!beRfuvc zepTh5hU)WAbdugD?CK#2{ZTcPpu05$#L`OLslJSFFNRcyQCu=HlDqyD(nAk3!6P)CSYs8= znR;X4CFZw3%agKtKRZ{1{U~fZma#yN{muosvxRe8W*;TO{5gdL89w>5H+B%mwY#rs z32BLt&)2_xq+jZw;K-|;6wVu&b6gG;s;ha-u%RJJ+4E?*Nyb98t=j+g!s#pvTOnz9 z=9%PmnF@VBX2@^$cn7+qgWS9Y&jW0zFJ;o5+rK}$`F&}5lUo1;HptOFpINbH`345H zRBwJhIq!d;2O>#)h2C8p7*@SzS$%(dm{BI}KL+Kzh@LYnPihH{!BrPR!>=7`GK4en^ z_!BY1GlewBrD1`bGURMg>bIVaijMyH%==`W!S%EF+3#^P^NAd}2ZPh`VmQZ{@w3!7 z!^mJBnaO6(B_k)jv zGHF66(^U`EUEv&59!(~s^?cTNmwJbVN3nhojJgi97;5n|S^g$8eAHF|Yh~Ss9n{~M0mnoz?T_~7D?2lguV(HSyynU}ua;N&^ zu_BWDpEY9MJqm~TXJ-a}a5Uvk#*{%fAQ^j}F5=|t9C!yWBU2N(+`ha1Yir(i0}d@V z@4?~ns~|)l-LUD2^ZNYVZZeNWJ-8eUd`Sst4yNS?_gOS!1=G50ay9}IhQaL457vve zsK)PUdCKfz0AaVwQ-=b)<~0Mp;!-$ zS3~K|GH5WLaJ=|xhd_qreg_0kp1k32g-W9>{(A*Uq*k%E*~j6Z`}*tkBO!J?*+QRK zP$#XFyjE$zK+i=$P6lDu)KRQF%}w}rgU!VK6SRnaZ!^0OjU6p-_h$!G&Vb&Y zh#vS$*|gD>!TT>b=?=)-UZLhgq+YM0Wc0?fq;8GRC!Qu0SkE7pO{VafwR;jnTz*&O zwwbnipIUkjMktx8-&8t?Z2X0PaI*BMcU*AKM?DH@-@w=3lfyiH4SvG?b1+V!Nyp>a z5YT|)-CPU2F;F~pY0u;#?iv?>lw1a>j*A=r_?N^)+|F4~U%gn%Xqqly)gP;;pD3vg zC-=+n1gGup+u#wC+dljE?}Q*!iBIY2&HqRRHfKCbo!s%iQiJrmJChKUuzDt$XLrS90q8>xjc?DC z33!06L88uQ2s-VN<)2nXvK8L`KpfPRo0!^XZ$I!PAaiGVNy`5hBb5RUMc5wSJj%Vq zQci0t5yaBIUg?d@pQ{~iE}CC!^y+D8qiN!g(NdQ#ey<Gl(fBE~ zX)98Nc~6QX(A2JxTb;pAUC#MKx0i|73LldR&)>j(##zL~t{4}q&XWKqQxE%1OFZ0< z^&1amlZBFG#tv4jliNx-en8vn=-D5k@<)r`d0EUS_O{8kAs7|=*Dr=K>#`P}z-pI{ zRy>VGk2${ChmD@_fapk3l@DmRd&kInXJnT;&lnb2!3NN|5!D)+^(ebF~MTvC?<6+|1cV;z&wFcFe zeGJXeHKYi*D9>5i#A=}9xcy8CY(xtb`ZYlDe3NCVpuD*hUZ=oGk3eZkhzVc+zZL+X zwP8ky_2?G?@9+-(_`p}AlQ?0YwTdc;xU}8|*!o>re(ej# zqgf1X)_+tlFQ*ar_%#}&88<^Zd2cTBg^HTGY?`1tGAeXtxM1YSoq%aEl_wB}lqyx7 zCK_ZT9%IK)3RCC0WhyJ01-*tmr}E&-rZC$ za$IJA8*X_z#)A>Ea3EbDST#;+hs?@H_x8_3ZYJs6E4o~o8zS5Q(ocXp>#D?g?X);gglfcAq1sa=DTkH12 zpc934Xt)lqW~!h~$-h1>(htT27+Yk}lv5i0g#UgPYW+9k7&6i9zS`>ZR|{1(Lbl20 z@2_)S=_iR-;~z61E~Y8rU$fQk@64X_@tw^C6EOJ~hBR~tA*dGu0uDNS$wYT1=R6wO z(4AA4*bS%wPzz>L2CXz{I-@E(plqD&FF8%TLwh7&aJ>zudvAcB%29rYuY#m+!`jdC zF)>IrcEtY^2^VY0W(v-w>uqrhK?GUrKLdJGiD=t7u4H zlfLSqJD21dlBuG2IPQ3SXtA_`&+v&&9^D-WJboG$sQdB9uJi6oYHeN+CoA#CDjD|x z{kG8qZr@5qCcQM|9O1jSTYzKk%~h`Hz^QG|JL^{nLuDWCuCa$35z5EU$TxT>-O@`#&PwT7-HVtT@%AXCpx@_z6j*6xaE949?O(y_z&DV z)q>IOvxJ-;KTHG}zs=S!?l)jdD`m`vmV~d*&?S6@-vK#Rmk8x!EA%!Yh@xq} zVeP#{OwvYV&)-78tOiD?XJ~9YKA5e(p5Xn}B9x z@HZ!iJ_Y4ns-}*1igNE#FsnQMWeW1+@_QvWDyYRt+eB?_JHD#Hu?H14kx{UW4n3NbF` zOC!6FcwLp6=u0WMEZm-8?d$5hSRM9FPP=f#?QJXF26Gn z3Hj!4=gtox>G0Cd2g!3+V9gnQAlObA79q9>9|IE1)cANMTRoXL`?!03QS99IDuH?o zDUZ?Buzxw+^H91bFUO`7iObI$^vwL1K$26`@CYvj!odf>$6CP0OwM!IP$mWkMX{fj zpt%Uq2b8#dOY!0ZpxFMpKf9!2usip=e*pMqE1$+yRg}(>9b-&-Z9Bil9b~dDkvk6n zV{0X=km-{f7?PI}Dv{DIK~|E|>l*-)Y$*^hoOmvDCeZQqSEm?^yWBGAWl?QmbD8xh zovZ(d^QMlkM)*21`e&DdZ&o%JghYdr`|6C5Iz@iKzaxvV%z%C?a?&~E_6e&gCk_^KkL*o@tRN6HKDS8NM)!?wv#<$#vRK&Glkx9o~4Wdu&flW14}<^g7B!3b9L`4$q8#=70Bbh(p%=+XRxD_69 z<$AYZ_v#r~lZTuTV*G15AYvj<6?5D4BKVlkq}djt<4r$&0C@hmZMOe~J&FrO)+U3d z{BHq8JcJ-?>XUL=TG>vChSb!`V~X-lS1KEgpgv`2SQWV}tJJnEZQuMUc0Q70pwur$ zA08TNdq$s*P?#s1N1pviyiJ#?X4Rf5^Xlexsl0?o9zt#`$K#MC*Bn>)j{PCaaq3+K z)y%h-MZ8>Nw1i)Gv}9kG_sAfP#<8}H{9TrXR|0sv@b7VKc*K+sClSe@^VI_BW?Az| zp$tiGGXRY5f$TE^&y^2#N2q<4iNDJ@lZf|}=9Rg=lVRVzX4_i|Amue_guwYkfwrZa zZal-~4PUSvbd(!Raqsle8*+5#O7Qv*8b2$z<(PO#2v>wsC0T+Vzi%#koSi(v;?an?nZaPmi0*;p`&4Vodd&6ow7H|4P{1DV{wX{~Q@!nH%Ny z`qx*Vm_+oN7xx&06*l^!7Vc{pEho}Cgv}d&Nlj7Azn_190UOQ^jz9e``KkcN+Pglv zm0P}}J70SM`fS`OSqike1Ou^-=SQ%RJOgco#{{FpO@vVe{7(=-#!MraL=5K<>$Vs8CXLv zYY*nV&eQ?u+AU`quS#y1f|O0(O_sz91g@yoNfdl9s|*NF>0QtsOE6CO3)o(BjLkyTmgqLFqAId(o8 z0*YL~FFEIsMNtL7J#MfBB>wxVDTIJLo}0ZNw}@Faukd)1W+W~cT(S2<4nOm##K02} zFt~^ZeI$vCZBfluD?-P&jDcX6<>}-ULhe=wZ18&}Bn&xj{ZinK*3Vlc;Z!}=@H**A zNjQB(N|-NXiUjDxJoTssEfJJ;@`xwE38ebajkz_8i!wBc1VdV)!>l9UP=03|3PJ0W zbeHy2tuZ8NbTKL!3oDBK@hXpuJMtVC`0O>hAcKI9giH*b9BYiKH{~<*D)YEMb%w*&U|8>R#1wu2kwd69Q z$Ap@nvTu{5(DXi!`6$B0Z)CfmxxOG-tC&{)jdkb z;p6674EG_wx+hpKL)GNwU_~Bemq1to1B?L}j?)f8Nj?G}4gI0x8ryw9Ym8E<7Wm?u zw`~~nS>4_-@tIGtg|449c0E&9{Ej4U*}}p1K;o=(m+Cu4?q6VFT1lupO_>#le}$0S zNf{+0`->Q_?5H$QuD{M}iMSsV+ug)eLur-j&M4@_+C)h76K<>*EiK?XPaqrUB5K=Z!&~|RVyz>HJd%}P`ltX{*bmF$u9^v zLPqazWYGw68@szcpvMks=m>RB^3JHtEA*4G>(HwJ$*I|R!MvDhbO>jxr`i{Eh9Gcg zlX|ebr}8-Y!3?PEb!NP4^R6~?1V}uAox}X+cH`IL=I^z>*@C2{RUS_)b{--~7IK3` zkS2hAw0aAqtS(M#o8R8g9OtVlukg7fo|#3d)|MtWMVWHqrQ5&orF0t!s%XG3vTSf4gRw<6|p$D2OR%!sBFG??rO$ETCavhSPc z(3dk50I(Ns0B1X6r!g8R1k@%~$?~h=Di0lzGtqw;#h817v6Yu^}|{Z7-fi3!zqFgr*!-VrgsDEW(xg5zz2aQhi`}#tumN z-Oftt9a|_tAu-c0uO>K(={-=OF%LMYP85G?1gWBdX@WyZ7+z0In00%;KSw4%)V;3V z*L>iLjeQYpc`~OmvFIna#Q1ios67s_rE9q|JW(^jcedR|Z94P3!|ZfoCzG4>f7Wbn zTM&}kpcY%XGv_0=)rC0p&NDGy{cW?R(auOpr66`yi3*%i@2p2HmOs9i1!^JL+DhKJ z)@urF(cKp`S1`JZ*dxqsMTY-Ph!AYI_9%`IK5q^SJF^OzWHbh1$bl+B(%}+0`M?hV zwrJDRBT7Q-7XzFTMyfz}(x%+velhNgV7KDQCXe6sL^1Y!VK3mb-8I8Thf#4NjGbgF z;Oo3_+R~I_T2bI6u~638e>U5(IBd>=K4>P9(wby2#Z8RKD5$hE`rrIJn@nXGtsnN9 zRru>`!J;jWD+bKYc7y|nQkm`(K|#&AY6aLjhdz)HjRu91ODuE=rYp@lXag=*2UnY; zr#ASxVD?u>tTjy&6N)YD&rzk}D>ipuI=Exal|nP$0v9xIO23_6<@V8{ z;7TznGDPcnKW*p;`Nk8?j~lYRFz961I#ODxH2Kz9{_d;&h{MlNyGkragzO*uh%?=I z=3ACi?uSt`>OTF$$6!oFER8S~)5GF=Wu%PHcF$<$Qu3U3QM428pu`ov>A%y54Ez(aVuxvg#y&9e&P zi1J!;-*I?_Nt62FSE|aB|JMQ*zwetu992Kj{TTGV`M3KLr@e|5Pnfal)g|Z>tGE)q z?_#e0yRNPNTv?80uzb@rS(S8Yr+`0QdaYAiF)8C}`I7=o(rZW8-~>UvCfR4@TmHBR z`!LY422mNDKinQ0AI?9ZdzL2sGoG0i4MYVBJmX=L&?i2WAbzn;dFxWFS~X{!4dm@~ z?BKdHO97QI@&MpLw+a3<-1&K8xk2_|6qO>GQ%Fq5lI^H37eIjVA6$Pk{xH5^%%Zq- zSbgUyc(s)w_$n_`@ZtTt3|R3i$*$9vZvf`MpOhAUk5W9Gn;W3@AYo0sKv6b))`;NM zJGg2v#!|od%J!RKv!q`*myCoFBBTKVR=C#nJRv!4N(x4=u#pLdq@=*uj1McHu@KUa*v=&@_|&9kIop=@+bj*UNHfBH)wT9w5W-To2D zP>ud7qXcbO-4wg3ritJ14KLs>_Ao=0MzVNW$E~2}_c+0p3$YM!$sb{LSMgFZ2;>j6 zNl%RT9dSw`xHQ2A{GVj%U0= zdKA2Nth}7-k34cBM@N@EH7N6Y@IUu%5&CWPLbrS3{|cWm5)#IumkNa36LZcJ+ARwz zTCQxA?4_8!7r#R?|6Y#r!y3&|0{feu{gPbq8oy<4M5UUj`27AT`%|=3B5HcmH_&R_ z=qd&bUo9mT47z|>#GNkCrFvZf%TnLqhqaMz>SR@FqS*Vp#woEdn2Z$uj^vqy9!eZ- z!SmG;YtPJ|#k%=nD*%u)Dvx!CS~`6y8o_UR&pFY+F>AAiL2^%u@dGG1CtA4JFuaL_T?~`8Y8AYjiEh#o`KWA zX?bg$_b0&4G4-PYCkZ(Gl+%iG3}sQ+2iA$-4dCW=F~W~%dZKc(x8xsOX<(F%Y~=5a zi%lztJt;PCVkGgzAyZCc3iS0BRkuF$5}H0IpXd*c>NNxu4UT?d>sH?kT0q05AM*wo zQw%dXWx0omCy}90G=(pQ4_t))4Dc?+vdF^2Uj~ma!Em&ArTJY$S=JM0a11pG%b>m> zqMx8%&3-EmW_PmQrSO$DpNz*0Fy|7g(~`z;S-pHHUebmDO8;ebXr0GbqF)>8Vl;my z^Hsbe4UN{7IOuQV_1t5o1uVKl)DmOs9zQgjxTgST{^Ji7d(|hohgd2VVROqNYd0Qhj{7-%K;_lk*w?{3fz>ngI za%~^r%pZMyM`6~LHq=1O^HI@}ewuBh!rHaPJCrjkv zQ}rW>r@bIOAJ0NjNMDwv3qh_lPA;l}oO*#)`UMXU?aCaYJ=}o6*1)$TF8S(|loU>m zX4i3`HBGAZVPPR*mntdfjlWS1ko~^(4E+Sen}&98o(N08Y<`*0M%kkP?(&u_x4C(S z_eC{}CNg{RbVF_MK_9*Cvu0HxqvGgv9o~Ku;JvgQqetNaDJ0BshCeS~_&o?Oq?cK@ z>4_>$>iz<0honm%&7qn|pekY3Y=+MknS7T*v81j(>7#>vd$H34Jketvl6Q2)hPAJS zK`9m$Qx3Qai%)W<#!Y_mHUT7ovL=;SVrA_zC?4X8J|cRRSL;*U+wnU{#-g>TYZGWJiyv{H)9nBP8Y4UMUpDd;+=H&^!G8h9vbyrKd@iC^^ljJI>t>sb%Ng+C7|u zb7ruxk2o~y& zC>&^9b$Z`bu<`cGp{Q8fqO+h|Zbe82+)Noe1*OGsLQUoCx{_`7HhVqbPpDW*-k^ z0oF{E@E3JiZB;oZwMT>8tBTx<`C=%_P{tF$2_y833F1n-WkNOh4}TElsb@>Kl`%## z(k6;PHF-sCygrc5Rh%6K0HcS}-mT{xN!j%0X+hrGGB6lcf+%UB0#PQ4`F4a_X(Zo> zVc*;4P^Kdu4MHka^7;e?kj|NTbs#^@C*A&jc6;~LN__%`LtqR~P+YsMgyfb}w) zme@k`SzjztPZ;hk7Wr_b&l8iz*qz{J0fRMpDgH@&_aaR`eqO8?tvapHUL~%RK zxQ_>i4QpfN8%Y6$$$y%{0#LqGo04638N3kgvG*x^bHVA~24@YcI0bz5vRqPe@t#Ez zm=Ke_R(dSaQXFw2Urcy9LvWO;&dP9c7hnAoVaE@e$JOd(8kHVC4yLy?pmpa_=A6lV zyxPJaISwp7Q$gmHK2V|D^4GD@$UdCT@0Zl28K`Pke~V8CY5nYth#9{4UQ@f*szO2K zH2*%r+k`n<7MLiBl|N|FxSa{{)5CXb?cJ{yTsrOxC1ro)W&M$TEPvLd6~^to^E z@7UxdZ{^P-ZL>h9n;!4lI>2KUY#wBoOgF2-Z7s$%&&&WO-u|b&q^ns$5HY=TS`jVV z>b2@qR4h<>Fj?c<71@gUvOwKt3Vx4ietw_+#><^y;+hclfuEsw@Edn?y<|HGsQJ8F zCJpgUsM_P`pz#5(Z>jiW<$~bed-jAU!438_QYd*;Un0<|jlTx<`7Rm6j=m^E8%F** z31#s|=1*>Ve}ALpiq~PS$oShq!~97WD8(0N{%`fxhmG-mkN=S zIsyR=b@_nv8T}-`_iunizMWWA+yBlIZ^)|G{uKBCG;`fI|DA63IaN}5!wK$-73p#C zvpyOY1y*Ab)+3zqsR;b&0jn7Se_GtzfuAd~cC-!6#jE?tX!7&|p3ce3W|C>$rGl$O zv$vyx(iQmGl0IjN>Io#noE=YkQ1Vf@V@V{(a-X>Zn2P zs+*!HZ?6a|3>Xnqk!I^){YSc}%Ou`U<5)Y)okPtTY(3LI@<+M%g7(;b?Frn5HNCXZ zj~PY0@zjN7C0IR+1}EyDyg|83KlzVu*Awr;0XYKj54#UTQt$5!z8P+92z@iq3eBfI z6~({H?q6btOnfk?4rCaFbx@2{F}7pH-;IM|9`6(54UAXHr%TLFNq5GZKL|KP6Vg!z zG=W}57aDfRiwG3BguCVsg5!KKgjMA7nfjoLa$?Jpv&?s12@^ywKW~#d`Fc|D7s|cF zd{L-*9R^+u6Y+z0-uu9k`9zMFg+}(3XFDU8_3GZe7jCxqH|I6M#U)X0)b$^XVI_Ls zJkh)%CcqN6Z1X**%#eBqzs}A1<6IRft62Mm-$K5Wr1w)rhVFM_Qn6Zq8JURjs3S`Y z=VdfSAO`UR#B9%>T&No^{SLfy>sOo{QDAO}_V$Xja-Pou**?E3O?EbSP$llEEp^)8 ze{1y{vd}p=;o;%6=Y*c?J4&Q(@&^5RN2z3Swb%=u<(WRL14As}p>^tYr+fA$lPnA` zRmlgQ%;|!&^^mNvY}dooy3`0H7-@bHWek+$p|cqz?vt*5L8427h$wj!$+S{u0L7o% zlx<@2+fC4mTj_ITG=N;nH%D9kWMU_>gBen$RbnrHl=U0h5_bjq>aMiEH!5W3kb8TlNTNqtxU!4!$ z?D_`6`&Fln4R|0Vh)yaI>X#k)i0*S5j~c{$P7vWBl)1hKNrJ%Y0etrRbYO9Qq|yyw z9jA;UG05@{!rIFTozD0|ElXd*(r24CNjO83hX&8kkQABHZ+-2evGh$YU>t>pYS*R=EFP3xJ6X0a3I9xi3kc z@hSwaScN6_b}un$R9-jM<1NaU^Q3OE(vY1olX}xm;l1%*ZD}x${MXD515mOwup8T#Oizwp{GoHgM=0(q%C zNezwR=1+jc`b;4*z~sQ6Iza>la$gPq2w1n8o_tOmG`M&2XObzwzc(q&7o#^x_v}oa z96a}{fIxwkQAxA+*EVb_&Tzqzkxc_V-mafrxKTHOOo20!dZW zQJBhtED3Gui(}EU1S-9LTgu~~ahbu zXW#%fWXhtR$iDjZqwh<`?uSIPfP0urA8eNJr>cxhhcf^MxbJ?OWU(?l(KYCOvlMk1 zF8zYsttNP#rh=Z}WAzs@8%Y#PN)qHe063%NwXsCMdix9^4I-MHLU2;pcG7YCt#jT< z&33iNs9iwbDbar|G^@BwHAx-ct6xnOMNvFW^CTqytK=d1upfAur5y+ocB#+#CePY# z_DcDB#M9vf@T-9=5=APOzVAA@@~9I=TJaP=zl?s7iH~(+3>e(N>bEv-1>cp41+9Yr1k^Tq`Bb#?-HY(B~K9^7r3>#5VT9*|)F~@^APBW^lbaMz0>; z`SBVnGl>s16a7KYX5Xij;<6p)cOR=bTN^`Q(m)(E1cC3$yPUCHz}gX+kCKv%^o`B8 zz!UxC5c~yw0^YD#j1lxTE-Bh`LHtioF49GkzNsz|23JJ5?1A!GhpPN^{fHpaW9|d? zP<#@|k5l*306s0xvhhG`{H78~#B6!V)7dL7RNh|5}YsIfnWg^y!~& ztin53?z>YgJG<9L?@}R~R^~#P?cR?M_vna6Q}9@E+Wz=(izzKFU5KtwlT+TjoFj*P z-I}t4yb3OwkG=9vOp1GH`U5)tQA%^(T==8&yzNb~0qJ7Ei4eg9Wq!TlScbUAX;r#4 z<`ag(~C|<1|ms^;kZ;H?6FJwFw!0|7XSqpyg@3* zY*J*zU+ZgM;yc6dNh)xE-2n*)Jwz<<+Y}#^7HVN#0|F5Kz;+vfJ(U?UWq16APtM=z zo>H;>4fFz-HU2^wyRHNXOkA7mwGXG19tbvTGj9XlTo^kt_@SR#V97tte(v+tHzWz1 zc!|eg1t+S5ub*WBf;!p9)}{wDRg>wdgkSOQJZ=BnC-fazP-Dh~@S|GjacJiH50G+V z*QcvaL73Nujuz!#Wj4X>9|b_;s;*Qze`g1*UVy3o`a`;Pghe(W95|22Q9t7J2|C4@ z;~G|dNB%*nY+4l={>l?$;Cos!#Eb^wL`^-F**#AqY(kI#_w~oRc<>^;emx2T9A3jp<}%`QgD!!ztJA z9^{yGGJaPl<7BC_n@{)ohVkd4HSJJv`wtORHmJOjHu8ljBGnApCfgCcxj?FVt&oUn zg?=^(Y9VC|g}+|Bj-~gFS0vzqiZf`XKbPM6v~7&O*x#(D$Nt3xAGzh@3~H4G&v}6a zvSdg8{);#*mYbNEcNZKw`;B8U>p5r)zwvX9I)q9< zS;iawERvvQz=T7Ep7c|JW}&LPTY~nythc?royYvsMah&fwWP}fn5A51ZT?twjtf&{ zv=@=dYaY>4gb~@e{HsA78@oW$8mBzH@kW&I0Qx^M%y3$}`)()U(<7iHYWtk3pmqIG z%3MM~tpDY_HGd8O;GX1*$iWdI`L>eMio0)P7$lGwEqnMs5V}@dN3bhI`HVx?AG(d3 z4}kjdlEu=bZsEBpJL&#btc>$h3?1Bvb$%keEELuGAZ^6njhI3hj{1**XE}h84{Jr@ z?fqj`azx4MB#HW|&ycVJR1NOf_cLd|2ICdf=*dH3)w9ULWaBtWV$u0gIBzAv>Vq0+ zP)V0hsqH0AZ#Kf|SUZn7FYRAJfh;u<5p+ICgCqau8EZq}Sm5ZLAH9ULXmzAq~#|r#dRBW)K@#W*eJKx_YkIXBjAj_tH&cB>`*n26a zO+V;t*r)zLNsny#uLV-4b(V>zp^%ekVPiMZ5fH$#A5ybz2=K0vmEM~=ZH*E)Q93%G zvK54dS=AV$oi0-T+38J#vV?u?UF>G2buA0qD9XHiGo9`<{qmCZZ-OFBW`GFn1yG!8 z1UGY`lE9gIYa1q8c^SCdsS)T*-4pB{*adcsg!pg>C7`D|%A=&KZJ-Qxw!d2%Bz&l; zrS}w)=`Q~#-8?ckzgn>{3=*}HvN#3rJ{Y2$l;Y+1t8!TE8Zu@BxdiCl13 z5UB2c*Bhxz|xcV9sDmN40JUvzW3KwA&p{Ka<|vRsqAcf^5WY z8>9ZITZ}9spH&AgRwmR|0~7c8>EpiZ@K^dC5)(si$;tfOHt|&h8w^-9&YwG04KQ{ zGkm$j(6#!nfHS*H7SSubj_UzRw{iQAdR*b+e_LoUrr(fo2rR8-b9*HqDkwo3vIqH} z;m8^PgvUrIb}u3au_*Ah!(!>>CQ48uP`-N|WoIm&?F$1*dlxt^{>%r1`XHqCtiMnf zt~lHF&PyibF`Ig3W6ZwzjS3q|lv67ak#MNM*iF^S_RKR3FA$%!fvtEE`y%lHcYn~=yTR!jOs*cQle z49Gy~(+nMpr<3CZter0Gp&u`*t1-+c7 ztZWX=i0p?X>KCFz!qs`?JCGMG$oOdORe^Ch%Bg?&sc&z-2ao1i-3XxFW6Mn*WMA+n zs95$L+}Y4gVmYG$b$Y8u$9&E3u`)31VTH?lGG=DD4P1x1>O z)1o7YJv`SYl=ELj`=RAkUiQFwZm#X-Fja!~;n!4L-XuxWRALIc zNju?g)Os5O|8+>lE1&CeFR(u`fTzX`C&cjVtUnv(J`@qJerw+G=Lz|rJG(Rl;|ZV9 zdGDgLCzrWIQ*Hfimz6d|A}Uan`?Eb&yyUw3UVANCcUpn|5M6gxrnLWJecvlHl`@97 zw7_CRSW&b_xFYOh)}t||#2Jpx1;Ws&dx0HGC-cw(T3Mgb+lu`5fzUd@BVPMr^Iz=H zx`Mr(qUBTO$+^n%uoI~F)eiE6tm{P zgY>^JYve=_XhrWjiU00f5k>Ef=OQt8wBYabFu&Gi;t2o@rL@?39;9#&SYxLQ_9{?_ z0ssoYCMWpmD5ug|g}kbq@`{kr%^|;YLjNrYx-zvQag<)x)YUu8>}`Q%D(AT^tN+Bn zIxj~R>OqQ($!gytLed?=jH~`G)9*P~0@Jw0g68E{sjwm~8MFh}=6kf*N2@pWlG+37 zIRCE&Xm`?JDz+1fRb$adi~6RX?%MGj1X}QYC;w~b76r-}EM=&$OsmmezbJRqNp$UP zHBP+90zt?tzw5KmLjj_0}_=yUDN@K=v!0-f^~ z7UMuVW?<{2N^o>*V2YFbfcBALVs7?XPwX4Cpw?8br_z+Aa~m4^IaTTo#r>rJ+irH` z^#3SEd^EKAP{AAs`EP2X)62F9EM!+N<}PTZAoF`}x&X*tZrPzog0R4|g*)f|tZ1)P{U+vNToH0Obg$sR7SA`{@4EcalNO3t# z7OXMqV((uVkhrO)gI|SkjjJr#@>d_V^5nD-Z1#^04E%njltwl_a(q%#qbEV52lMgj zy}r2-3I8tW-}z!b7FYozmueYjzmMkm{L?`f;S9gs)vZ4Pi%TgB(3pTt7@D5pIx{t` z-VrHE_koTj;6KSKAI1Rmd<}%>`#(7^JqJOW89lh+g%t}G1w$_-5Mg7UX0rr_YQ_+4 za`pFbgO&dPa{!ZSH8HF4<0l3N2L8n}S~R6V#+nftDKk6Q8TsE8Bs@B4M~SbCUkdxI zGGr002AOGGaAwJ3N_9Kh2^dFLU#eb;1ci4rcXV{*kBi+NAs2I9h_Ug|h`FvqP$m-l z9nVhcoR;8YRGwF?1=Sf6{i(9p?MUQHVWfYjfH0TgAjnYR1#97tdljR=)DV5Xw&h)vuU3q$YSS%Qg-tt0EfS#>N_R+{H=vct zE-03bE!~kiBbIZ^WH$W*?q)UPx3gtTemO1~V_n%J2pm$VtkLQ<{bWfJ>EUJ#X0;&@N3atYS#epZe<>+Y9Ew-WYTWEWr3tA5w>s?Qk$T9SeRu-+2L#9WH@+f~2D*^&8G?CD9;nD-7z1=C}XtsMuveK?C>? z81J6DlJ{PJT0ytkodbKK{Us)XJ$lGrI4ZtPZ9yvn9v{rg!EKZe8cb!6$odCXxtkGs zOORnRuD%(tzDx9cD$tyl`L_aH^?iQ$zN;E-G#q+;_ji1$TjcV^!n1AdBk)TdY_HT) zC}VPu6#@bt<@#eZ?l2)6LgHAel6be_2qc6>6_4!vT@|F4YJb{ddvS6apmDqt$%!GOs+j^G-cn)mTp;Y2xTX7Ge$MpMi@KE78TtXy9_fWOP8!8 zhLAA!CQMngi(-aIsu7ap_nv$2^Zfoj&pBt__sqL|-_OP|tM()N5z^I0L}TBlGF2iK zQzv8rk)>c;TRfDlb>Y-)*&v1Z&S2rNMd(!I4c;{3K*GtM9CA|U$Q>0iA1;7OX}=HS zPv%8cx8aZ6t8D@B$kX4xY#a`GU-S}T3wvsnI#x0m=JW^I3@}%BdhX>_^jj?~Apq@1 zv^p1!qyB}_ z`+%th=jlLY$at`txz(}{9pt)anHzTBIPyB^Ub6ki5=Fld@ZBeOAS;P{M{z%KU|}W{ z+07?YJzkOXENa6C)9S_!0X=;Y?^zMIG?dM52(h=431z>A07M&si8asfLN}kkZ!^Es z)QReT2!^|;sAFGxzN+517@d2!HFv8mfU&xrxFJ6Xyi$uQR|P09eNHVo7$l4Row>ryiGi{5F3t7guGyQ) zRD-|*cW1DBWCtQ=;hIQCv-3@UzITbzb@ik10+OglD9w{yc9(%~9P22rJQ|US8{q1u z2uM6st=6{z_>;N3vzR%fJbLF#X~E5$vo|plk+qSgi5Y9DvS0Kf%lyMNe`-$dz3_0k zpq+FuGs}MR6@f3X{$(Epgaqsk;q_)}D=T$3WoWFY_%eOs5R?2hcveis;YdLXKYsOt zx+{PR%~K*w^;`G5b~7+_4ZzP`6yZ@VGYeD(ALdHV9h#hSYpPz=^aph{!~gbC``C;p zGv2-!1y0dkXZI7_T!b4pDnnyZl@nOIAlCuoY8u!nx!MrLZG%x7`|abK^7X%yF5kW? zzS&palC)fu;o~w3L}428omid$n5jx^G`m;%$qu}NlMsVI2k4M=8RATSmh(m6O_fV= zW%rfI_A-(mctFH(boB-gJopKKv7h+I>V_(7+jlnmM}bUnf02&b?bw*|zg-@ZT+ zaeEd`K$EN9@&+NDXnXQ@C7mNBugfAwTI-p$i~aAURQ_Pco@av}J+vFpb#@?HfxKXx zYDxdDO8rv1*H75G)D&8NIKaPC?~DmgZ2O4=ci&ztn-W#hF*ndF0%y4^+|3GyV|ak< z&o~WMOrR0{If0ln^lQtaZr$#c zucQwz0HUh6+Df^*=qw@{TcW1vQlxU1qyX>765k{_#hHBU< zJ1GsIDkKjX!&qGg0YY+uo{db9yMnDA)*X~MW>0f(0s1=YC@<1jN~KBi6X!N)t2yP> ze;5-M-1iAbvAcaBsMcoy6_!5-*_%}x!d_i$ey}_Dr{w9cI#eJ*SMTq$J{7;j8KU)L zd8CxlJ)hN{!jd~9lSsX;c2$evQ|U0@2JcZsznA=MG4mi{$@VZnutc*xdC<;l^@V`p z*tOsdS3WA@iv^4PbQ^ye3c~250J%gfwo2yJtw}BS+@rz#Sm(TAY#^q^{q}Kb?6SxN z<+WMM(JCYTW3er-Yz#%vPubp*zq*@4B))-BiTxcptPz>$qlEB=cPfSRc~90B1Icx9 ztR|Yd$Q5bsGE27CB6=~sCMb<9QS>gP%ni29+XUbHzGqYfy|^18xiEfFsk zBp8`=ofMXOUR5J2ERN$tDMiW;irq;Q{fg2Ja=qnrym-<0FABt1cL@Xykp2+Z`v!Mt z&bn$dbh+m}pGI$RO)$Z=Iuppl8Z&A>1z~O#t+}{&Q|5b`UOUvPvP`wAROUs+0}#fC zMxO&@t`;}u5LqU;+pE-`qTf}O3Dbe#o4+vFZm~i|B932Bm8wpzVvUrV2+aB@p8Js6Am5UTh#Z-Nnt)cDBQZef2 zy!$$V`6I;r#s9=pdB7Daw?cHg0GQ8;kTy7lb@M`I0=C(daROd(=?>hp83n?Ug0>%r zI_B8b#NR5cuyaZR(E3<}u@v8^U<;wq#*Wya)N<%=q>%`mdaA2u zoj(LtmnTUdjvVDSnKk|ZiWg-xi#>b+G$2~YptEb5M)#@HSA0|<;!nEcuTUW@g~dw1 znNyD^4Erj6bgq6LhR0FA_Z68!Z0tOEysdmiV1K`f4A|@_y(?eq`{a&Zf`_tc8P=Uz zBSn_?3E6ej3O6X|tXFL~iaA&!#|GRrH;fCUoKLA?od&wR8u~~!s6ROr_TuTYAd{$^agsHOwBX~(7lKM z0_;JDjhUQDE6Pi6wGqdCo}H!RDYPKNerW?rkC%<#(*zTbe>w@2LU)tRey(?GAtF-^ z1H`oVGemfYv#GZ0CbtM{JMSxv#N~8vII}IWx3Z~=srXA+Ycl2E$I((9Zd~ZK!EDQ%Z>rxl zm9JZx+{z9zFft?wlf=A1jjQ3GPX5^+tq+`B2dJm44jQYfMfpf=W(C6Ns%4s`cDN9W z!fecyE}mt?4|d$xJZR^C4O9NFVfbM6D^)04=%>+<$kNDOWw!;i66bKu0XGpA`$bH!$43v)}++ANt(qpRQn7Lb>`E=rtLQ+ESV3>wl8}0=G~+SibFv z0I{~da_{{@Js693R??GG`V6i|n8`GmyS_hj=!O`6I%}TB?o%yEH!=$^)!$lAG4e=- zp{N$P>c{84W*A~`-a5+@(E`$#t|JB4(g9R_V#a(&cg3znNZu@k4Niv9B0*V0!~?BD zUzlmFLW0DO1CIce7!ub{>}grwgTr8Zd&j$DpyWqR=x^hISum+`;VCJ(;0nyr)Yhcx IjBDKg0E}x2(f|Me literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_sound.png b/frogpilot/assets/toggle_icons/icon_sound.png new file mode 100644 index 0000000000000000000000000000000000000000..68d0ace418dc9b7ac48981d6aba5a09032e70931 GIT binary patch literal 12172 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 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_speed_map.png b/frogpilot/assets/toggle_icons/icon_speed_map.png new file mode 100644 index 0000000000000000000000000000000000000000..4557dd5fad5c3f54227a545097d33466a7bd2e04 GIT binary patch literal 25586 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 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_steering.png b/frogpilot/assets/toggle_icons/icon_steering.png new file mode 100644 index 0000000000000000000000000000000000000000..1f987418ac9da714ca0cac15fffbbbca3da92bcb GIT binary patch literal 20946 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 kxpq>ABOo)KCt!4fEf1%(%vj6}9 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_system.png b/frogpilot/assets/toggle_icons/icon_system.png new file mode 100644 index 0000000000000000000000000000000000000000..c3d82613f39b755c59f69be67f8c708ba7353e4e GIT binary patch literal 20439 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 literal 0 HcmV?d00001 diff --git a/frogpilot/assets/toggle_icons/icon_tuning.png b/frogpilot/assets/toggle_icons/icon_tuning.png new file mode 100644 index 0000000000000000000000000000000000000000..8c2fa95ca96586fc22415416e96b0f91e183824a GIT binary patch literal 6495 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 +} diff --git a/frogpilot/ui/qt/offroad/data_settings.h b/frogpilot/ui/qt/offroad/data_settings.h new file mode 100644 index 000000000..fe06cce48 --- /dev/null +++ b/frogpilot/ui/qt/offroad/data_settings.h @@ -0,0 +1,21 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotDataPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotDataPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +signals: + void openSubPanel(); + +private: + bool forceOpenDescriptions; + bool isMetric; + + FrogPilotSettingsWindow *parent; + + Params params; +}; diff --git a/frogpilot/ui/qt/offroad/device_settings.cc b/frogpilot/ui/qt/offroad/device_settings.cc new file mode 100644 index 000000000..0c957e6aa --- /dev/null +++ b/frogpilot/ui/qt/offroad/device_settings.cc @@ -0,0 +1,220 @@ +#include "frogpilot/ui/qt/offroad/device_settings.h" + +FrogPilotDevicePanel::FrogPilotDevicePanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + QStackedLayout *deviceLayout = new QStackedLayout(); + addItem(deviceLayout); + + FrogPilotListWidget *deviceList = new FrogPilotListWidget(this); + + ScrollView *devicePanel = new ScrollView(deviceList, this); + + deviceLayout->addWidget(devicePanel); + + FrogPilotListWidget *deviceManagementList = new FrogPilotListWidget(this); + FrogPilotListWidget *screenList = new FrogPilotListWidget(this); + + ScrollView *deviceManagementPanel = new ScrollView(deviceManagementList, this); + ScrollView *screenPanel = new ScrollView(screenList, this); + + deviceLayout->addWidget(deviceManagementPanel); + deviceLayout->addWidget(screenPanel); + + const std::vector> deviceToggles { + {"DeviceManagement", tr("Device Settings"), tr("Settings that control how the device runs, powers off, and manages driving data."), "../../frogpilot/assets/toggle_icons/icon_device.png"}, + {"DeviceShutdown", tr("Device Shutdown Timer"), tr("Keep the device on for the set amount of time after a drive before it shuts down automatically."), ""}, + {"NoLogging", tr("Disable Logging"), QString("%1

%2").arg(tr("WARNING: This will prevent your drives from being recorded and all data will be unobtainable!")).arg(tr("Prevent the device from saving driving data.")), ""}, + {"NoUploads", tr("Disable Uploads"), QString("%1

%2").arg(tr("WARNING: This will prevent your drives from being uploaded to comma connect which will impact debugging and official support from comma!")).arg(tr("Prevent the device from uploading driving data.")), ""}, + {"HigherBitrate", tr("High-Quality Recording"), tr("Save drive footage in higher video quality."), ""}, + {"LowVoltageShutdown", tr("Low-Voltage Cutoff"), tr("While parked, if the battery voltage falls below the set level, the device shuts down to prevent excessive battery drain."), ""}, + {"IncreaseThermalLimits", tr("Raise Temperature Limits"), QString("%1

%2").arg(tr("WARNING: Running at higher temperatures may damage your device!")).arg(tr("Allow the device to run at higher temperatures before throttling or shutting down. Use only if you understand the risks!")), ""}, + + {"ScreenManagement", tr("Screen Settings"), tr("Settings that control screen brightness, screen recording, and timeout duration."), "../../frogpilot/assets/toggle_icons/icon_light.png"}, + {"ScreenBrightness", tr("Screen Brightness (Offroad)"), tr("The screen brightness while not driving."), ""}, + {"ScreenBrightnessOnroad", tr("Screen Brightness (Onroad)"), tr("The screen brightness while driving."), ""}, + {"ScreenRecorder", tr("Screen Recorder"), tr("Add a button to the driving screen to record the display."), ""}, + {"ScreenTimeout", tr("Screen Timeout (Offroad)"), tr("How long the screen stays on after being tapped while not driving."), ""}, + {"ScreenTimeoutOnroad", tr("Screen Timeout (Onroad)"), tr("How long the screen stays on after being tapped while driving."), ""}, + {"StandbyMode", tr("Standby Mode"), tr("Turn the screen off while driving and automatically wake it up for alerts or engagement state changes."), ""}, + + {"IgnoreMe", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""}, + {"IgnoreMe2", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""}, + {"IgnoreMe3", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""}, + {"IgnoreMe4", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""}, + {"IgnoreMe5", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""} + }; + + for (const auto &[param, title, desc, icon] : deviceToggles) { + AbstractControl *deviceToggle; + + if (param == "DeviceManagement") { + FrogPilotManageControl *deviceManagementToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(deviceManagementToggle, &FrogPilotManageControl::manageButtonClicked, [deviceLayout, deviceManagementPanel]() { + deviceLayout->setCurrentWidget(deviceManagementPanel); + }); + deviceToggle = deviceManagementToggle; + } else if (param == "DeviceShutdown") { + std::map shutdownLabels; + for (int i = 0; i <= 33; ++i) { + shutdownLabels[i] = i == 0 ? tr("5 mins") : i <= 3 ? QString::number(i * 15) + tr(" mins") : QString::number(i - 3) + (i == 4 ? tr(" hour") : tr(" hours")); + } + deviceToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 33, QString(), shutdownLabels); + } else if (param == "NoUploads") { + std::vector uploadsToggles{"DisableOnroadUploads"}; + std::vector uploadsToggleNames{tr("Disable Onroad Only")}; + deviceToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, uploadsToggles, uploadsToggleNames); + } else if (param == "LowVoltageShutdown") { + deviceToggle = new FrogPilotParamValueControl(param, title, desc, icon, 11.8, 12.5, tr(" volts"), std::map(), 0.1); + + } else if (param == "ScreenManagement") { + FrogPilotManageControl *screenToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(screenToggle, &FrogPilotManageControl::manageButtonClicked, [deviceLayout, screenPanel]() { + deviceLayout->setCurrentWidget(screenPanel); + }); + deviceToggle = screenToggle; + } else if (param == "ScreenBrightness" || param == "ScreenBrightnessOnroad") { + std::map brightnessLabels; + int minBrightness = (param == "ScreenBrightnessOnroad") ? 0 : 1; + for (int i = 0; i <= 101; ++i) { + brightnessLabels[i] = i == 0 ? tr("Screen Off") : i == 101 ? tr("Auto") : QString::number(i) + "%"; + } + deviceToggle = new FrogPilotParamValueControl(param, title, desc, icon, minBrightness, 101, QString(), brightnessLabels, 1, true); + } else if (param == "ScreenTimeout" || param == "ScreenTimeoutOnroad") { + deviceToggle = new FrogPilotParamValueControl(param, title, desc, icon, 5, 60, tr(" seconds"), {}, 5); + + } else { + deviceToggle = new ParamControl(param, title, desc, icon); + } + + toggles[param] = deviceToggle; + + if (deviceManagementKeys.contains(param)) { + deviceManagementList->addItem(deviceToggle); + } else if (screenKeys.contains(param)) { + screenList->addItem(deviceToggle); + } else { + deviceList->addItem(deviceToggle); + + parentKeys.insert(param); + } + + if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast(deviceToggle)) { + QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { + emit openSubPanel(); + openDescriptions(forceOpenDescriptions, toggles); + }); + } + + QObject::connect(deviceToggle, &AbstractControl::hideDescriptionEvent, [this]() { + update(); + }); + QObject::connect(deviceToggle, &AbstractControl::showDescriptionEvent, [this]() { + update(); + }); + } + + static_cast(toggles["IncreaseThermalLimits"])->setConfirmation(true, false); + static_cast(toggles["NoLogging"])->setConfirmation(true, false); + static_cast(toggles["NoUploads"])->setConfirmation(true, false); + + QSet brightnessKeys = {"ScreenBrightness", "ScreenBrightnessOnroad"}; + for (const QString &key : brightnessKeys) { + FrogPilotParamValueControl *paramControl = static_cast(toggles[key]); + QObject::connect(paramControl, &FrogPilotParamValueControl::valueChanged, [key, this](int value) { + if (!started && key == "ScreenBrightness") { + Hardware::set_brightness(value); + } else if (started && key == "ScreenBrightnessOnroad") { + Hardware::set_brightness(value); + } + }); + } + + QSet forceUpdateKeys = {"NoUploads"}; + for (const QString &key : forceUpdateKeys) { + QObject::connect(static_cast(toggles[key]), &FrogPilotButtonToggleControl::buttonClicked, this, &FrogPilotDevicePanel::updateToggles); + QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, this, &FrogPilotDevicePanel::updateToggles); + } + + QSet rebootKeys = {"HigherBitrate"}; + for (const QString &key : rebootKeys) { + QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) { + QString filePath; + if (key == "HigherBitrate") { + filePath = "/cache/use_HD"; + } + + if (!filePath.isEmpty()) { + QFile toggleFile(filePath); + if (state) { + if (!toggleFile.exists()) { + toggleFile.open(QIODevice::WriteOnly); + toggleFile.close(); + } + } else { + if (toggleFile.exists()) { + toggleFile.remove(); + } + } + } + + if (FrogPilotConfirmationDialog::toggleReboot(this)) { + Hardware::reboot(); + } + }); + } + + openDescriptions(forceOpenDescriptions, toggles); + + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [deviceLayout, devicePanel, this] { + openDescriptions(forceOpenDescriptions, toggles); + deviceLayout->setCurrentWidget(devicePanel); + }); + QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotDevicePanel::updateState); +} + +void FrogPilotDevicePanel::showEvent(QShowEvent *event) { + updateToggles(); +} + +void FrogPilotDevicePanel::updateState(const UIState &s) { + if (!isVisible()) { + return; + } + + started = s.scene.started; +} + +void FrogPilotDevicePanel::updateToggles() { + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + toggle->setVisible(false); + } + } + + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + continue; + } + + bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble(); + + if (key == "HigherBitrate") { + setVisible &= params.getBool("DeviceManagement") && params.getBool("NoUploads") && !params.getBool("DisableOnroadUploads"); + } + + toggle->setVisible(setVisible); + + if (setVisible) { + if (deviceManagementKeys.contains(key)) { + toggles["DeviceManagement"]->setVisible(true); + } else if (screenKeys.contains(key)) { + toggles["ScreenManagement"]->setVisible(true); + } + } + } + + openDescriptions(forceOpenDescriptions, toggles); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/device_settings.h b/frogpilot/ui/qt/offroad/device_settings.h new file mode 100644 index 000000000..7a281bae7 --- /dev/null +++ b/frogpilot/ui/qt/offroad/device_settings.h @@ -0,0 +1,34 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotDevicePanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotDevicePanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +signals: + void openSubPanel(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void updateState(const UIState &s); + void updateToggles(); + + bool forceOpenDescriptions; + bool started; + + std::map toggles; + + QSet deviceManagementKeys = {"DeviceShutdown", "HigherBitrate", "IncreaseThermalLimits", "LowVoltageShutdown", "NoLogging", "NoUploads"}; + QSet screenKeys = {"ScreenBrightness", "ScreenBrightnessOnroad", "ScreenRecorder", "ScreenTimeout", "ScreenTimeoutOnroad", "StandbyMode"}; + + QSet parentKeys; + + FrogPilotSettingsWindow *parent; + + Params params; +}; diff --git a/frogpilot/ui/qt/offroad/frogpilot_settings.cc b/frogpilot/ui/qt/offroad/frogpilot_settings.cc new file mode 100644 index 000000000..64f365351 --- /dev/null +++ b/frogpilot/ui/qt/offroad/frogpilot_settings.cc @@ -0,0 +1,425 @@ +#include "frogpilot/ui/qt/offroad/data_settings.h" +#include "frogpilot/ui/qt/offroad/device_settings.h" +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" +#include "frogpilot/ui/qt/offroad/lateral_settings.h" +#include "frogpilot/ui/qt/offroad/longitudinal_settings.h" +#include "frogpilot/ui/qt/offroad/maps_settings.h" +#include "frogpilot/ui/qt/offroad/model_settings.h" +#include "frogpilot/ui/qt/offroad/navigation_settings.h" +#include "frogpilot/ui/qt/offroad/sounds_settings.h" +#include "frogpilot/ui/qt/offroad/theme_settings.h" +#include "frogpilot/ui/qt/offroad/utilities.h" +#include "frogpilot/ui/qt/offroad/vehicle_settings.h" +#include "frogpilot/ui/qt/offroad/visual_settings.h" +#include "frogpilot/ui/qt/offroad/wheel_settings.h" + +void FrogPilotSettingsWindow::createPanelButtons(FrogPilotListWidget *list) { + FrogPilotDataPanel *frogpilotDataPanel = new FrogPilotDataPanel(this, !shownDescriptions.value("FrogPilotDataPanel").toBool(false)); + FrogPilotDevicePanel *frogpilotDevicePanel = new FrogPilotDevicePanel(this, !shownDescriptions.value("FrogPilotDevicePanel").toBool(false)); + FrogPilotLateralPanel *frogpilotLateralPanel = new FrogPilotLateralPanel(this, !shownDescriptions.value("FrogPilotLateralPanel").toBool(false)); + FrogPilotLongitudinalPanel *frogpilotLongitudinalPanel = new FrogPilotLongitudinalPanel(this, !shownDescriptions.value("FrogPilotLongitudinalPanel").toBool(false)); + FrogPilotMapsPanel *frogpilotMapsPanel = new FrogPilotMapsPanel(this, !shownDescriptions.value("FrogPilotMapsPanel").toBool(false)); + FrogPilotModelPanel *frogpilotModelPanel = new FrogPilotModelPanel(this, !shownDescriptions.value("FrogPilotModelPanel").toBool(false)); + FrogPilotNavigationPanel *frogpilotNavigationPanel = new FrogPilotNavigationPanel(this, !shownDescriptions.value("FrogPilotNavigationPanel").toBool(false)); + FrogPilotSoundsPanel *frogpilotSoundsPanel = new FrogPilotSoundsPanel(this, !shownDescriptions.value("FrogPilotSoundsPanel").toBool(false)); + FrogPilotThemesPanel *frogpilotThemesPanel = new FrogPilotThemesPanel(this, !shownDescriptions.value("FrogPilotThemesPanel").toBool(false)); + FrogPilotUtilitiesPanel *frogpilotUtilitiesPanel = new FrogPilotUtilitiesPanel(this, !shownDescriptions.value("FrogPilotUtilitiesPanel").toBool(false)); + FrogPilotVehiclesPanel *frogpilotVehiclesPanel = new FrogPilotVehiclesPanel(this, !shownDescriptions.value("FrogPilotVehiclesPanel").toBool(false)); + FrogPilotVisualsPanel *frogpilotVisualsPanel = new FrogPilotVisualsPanel(this, !shownDescriptions.value("FrogPilotVisualsPanel").toBool(false)); + FrogPilotWheelPanel *frogpilotWheelPanel = new FrogPilotWheelPanel(this, !shownDescriptions.value("FrogPilotWheelPanel").toBool(false)); + + std::vector>> panelButtons = { + {{tr("MANAGE"), frogpilotSoundsPanel}}, + {{tr("DRIVING MODEL"), frogpilotModelPanel}, {tr("GAS / BRAKE"), frogpilotLongitudinalPanel}, {tr("STEERING"), frogpilotLateralPanel}}, + {{tr("MAP DATA"), frogpilotMapsPanel}, {tr("NAVIGATION"), frogpilotNavigationPanel}}, + {{tr("DATA"), frogpilotDataPanel}, {tr("DEVICE CONTROLS"), frogpilotDevicePanel}, {tr("UTILITIES"), frogpilotUtilitiesPanel}}, + {{tr("APPEARANCE"), frogpilotVisualsPanel}, {tr("THEME"), frogpilotThemesPanel}}, + {{tr("VEHICLE SETTINGS"), frogpilotVehiclesPanel}, {tr("WHEEL CONTROLS"), frogpilotWheelPanel}} + }; + + std::vector> panelInfo = { + {tr("Alerts and Sounds"), tr("Adjust alert volumes and enable custom notifications."), "../../frogpilot/assets/toggle_icons/icon_sound.png"}, + {tr("Driving Controls"), tr("Fine-tune custom FrogPilot acceleration, braking, and steering controls."), "../../frogpilot/assets/toggle_icons/icon_steering.png"}, + {tr("Navigation"), tr("Download map data for the \"Speed Limit Controller\"."), "../../frogpilot/assets/toggle_icons/icon_navigate.png"}, + {tr("System Settings"), tr("Manage backups, device settings, screen options, storage, and tools to keep FrogPilot running smoothly."), "../../frogpilot/assets/toggle_icons/icon_system.png"}, + {tr("Theme and Appearance"), tr("Customize the look of the driving screen and interface, including themes!"), "../../frogpilot/assets/toggle_icons/icon_display.png"}, + {tr("Vehicle Settings"), tr("Configure car-specific options and steering wheel button mappings."), "../../frogpilot/assets/toggle_icons/icon_vehicle.png"} + }; + + for (size_t i = 0; i < panelInfo.size(); ++i) { + const QString &title = std::get<0>(panelInfo[i]); + const QString &description = std::get<1>(panelInfo[i]); + const QString &icon = std::get<2>(panelInfo[i]); + + const std::vector> &widgetLabels = panelButtons[i]; + + std::vector labels; + std::vector widgets; + + for (size_t j = 0; j < widgetLabels.size(); ++j) { + labels.push_back(std::get<0>(widgetLabels[j])); + + QWidget *panel = std::get<1>(widgetLabels[j]); + panel->setContentsMargins(50, 25, 50, 25); + + ScrollView *panelFrame = new ScrollView(panel, this); + mainLayout->addWidget(panelFrame); + widgets.push_back(panelFrame); + } + + FrogPilotButtonsControl *panelButton = new FrogPilotButtonsControl(title, description, icon, labels); + if (title == tr("Alerts and Sounds")) soundPanelButtons = panelButton; + if (title == tr("Driving Controls")) drivingPanelButtons = panelButton; + if (title == tr("Navigation")) navigationPanelButtons = panelButton; + if (title == tr("System Settings")) systemPanelButtons = panelButton; + if (title == tr("Theme and Appearance")) themePanelButtons = panelButton; + if (title == tr("Vehicle Settings")) vehiclePanelButtons = panelButton; + + if (forceOpenDescriptions) { + panelButton->showDescription(); + } + + QObject::connect(panelButton, &FrogPilotButtonsControl::buttonClicked, [widgets, this](int id) { + mainLayout->setCurrentWidget(widgets[id]); + + panelOpen = true; + + openPanel(); + + ScrollView *panelFrame = qobject_cast(widgets[id]); + if (panelFrame) { + QWidget *panel = panelFrame->widget(); + QString className = panel->metaObject()->className(); + + if (!shownDescriptions.value(className).toBool(false)) { + shownDescriptions.insert(className, true); + params.putNonBlocking("ShownToggleDescriptions", QJsonDocument(shownDescriptions).toJson(QJsonDocument::Compact).toStdString()); + } + } + }); + + list->addItem(panelButton); + } + + QObject::connect(frogpilotDataPanel, &FrogPilotDataPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotDevicePanel, &FrogPilotDevicePanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotLateralPanel, &FrogPilotLateralPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotLongitudinalPanel, &FrogPilotLongitudinalPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotLongitudinalPanel, &FrogPilotLongitudinalPanel::openSubSubPanel, this, &FrogPilotSettingsWindow::openSubSubPanel); + QObject::connect(frogpilotLongitudinalPanel, &FrogPilotLongitudinalPanel::openSubSubSubPanel, this, &FrogPilotSettingsWindow::openSubSubSubPanel); + QObject::connect(frogpilotMapsPanel, &FrogPilotMapsPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotModelPanel, &FrogPilotModelPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotNavigationPanel, &FrogPilotNavigationPanel::closeSubPanel, this, &FrogPilotSettingsWindow::closeSubPanel); + QObject::connect(frogpilotNavigationPanel, &FrogPilotNavigationPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotSoundsPanel, &FrogPilotSoundsPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotThemesPanel, &FrogPilotThemesPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotVehiclesPanel, &FrogPilotVehiclesPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotVisualsPanel, &FrogPilotVisualsPanel::openSubPanel, this, &FrogPilotSettingsWindow::openSubPanel); + QObject::connect(frogpilotVisualsPanel, &FrogPilotVisualsPanel::openSubSubPanel, this, &FrogPilotSettingsWindow::openSubSubPanel); +} + +FrogPilotSettingsWindow::FrogPilotSettingsWindow(SettingsWindow *parent) : QFrame(parent) { + shownDescriptions = QJsonDocument::fromJson(QString::fromStdString(params.get("ShownToggleDescriptions")).toUtf8()).object(); + + QString className = this->metaObject()->className(); + if (!shownDescriptions.value(className).toBool(false)) { + forceOpenDescriptions = true; + } + + mainLayout = new QStackedLayout(this); + + QWidget *frogpilotWidget = new QWidget(this); + QVBoxLayout *frogpilotLayout = new QVBoxLayout(frogpilotWidget); + frogpilotLayout->setContentsMargins(50, 25, 50, 25); + frogpilotWidget->setLayout(frogpilotLayout); + + frogpilotPanel = new ScrollView(frogpilotWidget, this); + mainLayout->addWidget(frogpilotPanel); + frogpilotPanel->setWidget(frogpilotWidget); + + FrogPilotListWidget *list = new FrogPilotListWidget(this); + frogpilotLayout->addWidget(list); + + std::vector togglePresets{tr("Minimal"), tr("Standard"), tr("Advanced"), tr("Developer")}; + togglePreset = new FrogPilotButtonsControl(tr("Tuning Level"), + tr("Choose your tuning level. Lower levels keep it simple; higher levels unlock more toggles for finer control.\n\n" + "Minimal - Ideal for those who prefer simplicity or ease of use\n" + "Standard - Recommended for most users for a balanced experience\n" + "Advanced - Fine-tuning for experienced users\n" + "Developer - Highly customizable settings for seasoned enthusiasts"), + "../../frogpilot/assets/toggle_icons/icon_tuning.png", togglePresets, true); + QObject::connect(togglePreset, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + tuningLevel = id; + + params.putIntNonBlocking("TuningLevel", tuningLevel); + + updateVariables(); + + emit tuningLevelChanged(tuningLevel); + + if (id == 3) { + ConfirmationDialog::alert(tr("WARNING: These settings are risky and can drastically change how openpilot drives. Only change if you fully understand what they do!"), this); + } + }); + togglePreset->setCheckedButton(params.getInt("TuningLevel")); + if (forceOpenDescriptions) { + togglePreset->showDescription(); + } + list->addItem(togglePreset, true); + + createPanelButtons(list); + + QObject::connect(parent, &SettingsWindow::closePanel, this, &FrogPilotSettingsWindow::closePanel); + QObject::connect(parent, &SettingsWindow::closeSubPanel, this, &FrogPilotSettingsWindow::closeSubPanel); + QObject::connect(parent, &SettingsWindow::closeSubSubPanel, this, &FrogPilotSettingsWindow::closeSubSubPanel); + QObject::connect(parent, &SettingsWindow::closeSubSubSubPanel, this, &FrogPilotSettingsWindow::closeSubSubSubPanel); + QObject::connect(parent, &SettingsWindow::updateMetric, this, &FrogPilotSettingsWindow::updateMetric); + QObject::connect(parent, &SettingsWindow::updateTuningLevel, this, &FrogPilotSettingsWindow::updateTuningLevel); + QObject::connect(uiState(), &UIState::offroadTransition, this, &FrogPilotSettingsWindow::updateVariables); + QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotSettingsWindow::updateState); + + std::vector keys = params.allKeys(); + for (std::vector::const_iterator it = keys.begin(); it != keys.end(); ++it) { + const std::string &key = *it; + frogpilotToggleLevels[QString::fromStdString(key)] = params.getTuningLevel(key); + } + tuningLevel = params.getInt("TuningLevel"); + + closeSubPanel(); + updateMetric(params.getBool("IsMetric"), true); + updateVariables(); +} + +void FrogPilotSettingsWindow::updateTuningLevel() { + tuningLevel = params.getInt("TuningLevel"); + togglePreset->setCheckedButton(params.getInt("TuningLevel")); + + updateVariables(); + + emit tuningLevelChanged(tuningLevel); +} + +void FrogPilotSettingsWindow::showEvent(QShowEvent *event) { + static bool alertShown = false; + + QString className = this->metaObject()->className(); + if (!shownDescriptions.value(className).toBool(false)) { + shownDescriptions.insert(className, true); + params.putNonBlocking("ShownToggleDescriptions", QJsonDocument(shownDescriptions).toJson(QJsonDocument::Compact).toStdString()); + } + + if (forceOpenDescriptions) { + togglePreset->showDescription(); + + drivingPanelButtons->showDescription(); + navigationPanelButtons->showDescription(); + soundPanelButtons->showDescription(); + systemPanelButtons->showDescription(); + themePanelButtons->showDescription(); + vehiclePanelButtons->showDescription(); + + if (!alertShown) { + ConfirmationDialog::alert(tr("All toggle descriptions are currently expanded. You can tap a toggle's name to open or close its description at any time!"), this); + alertShown = true; + } + } +} + +void FrogPilotSettingsWindow::hideEvent(QHideEvent *event) { + closePanel(); +} + +void FrogPilotSettingsWindow::closePanel() { + if (forceOpenDescriptions) { + togglePreset->showDescription(); + + drivingPanelButtons->showDescription(); + navigationPanelButtons->showDescription(); + soundPanelButtons->showDescription(); + systemPanelButtons->showDescription(); + themePanelButtons->showDescription(); + vehiclePanelButtons->showDescription(); + } + + mainLayout->setCurrentWidget(frogpilotPanel); + + panelOpen = false; +} + +void FrogPilotSettingsWindow::updateState() { + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + frogpilot_scene.frogpilot_panel_active = panelOpen && keepScreenOn; +} + +void FrogPilotSettingsWindow::updateVariables() { + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + QJsonObject &frogpilot_toggles = frogpilot_scene.frogpilot_toggles; + + isFrogsGoMoo = ::isFrogsGoMoo(); + + std::string carParams = params.get("CarParamsPersistent"); + if (!carParams.empty()) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(carParams.data(), carParams.size())); + cereal::CarParams::Reader CP = cmsg.getRoot(); + cereal::CarParams::SafetyModel safetyModel = CP.getSafetyConfigs()[0].getSafetyModel(); + + std::string carFingerprint = CP.getCarFingerprint(); + carMake = CP.getBrand(); + + friction = CP.getLateralTuning().getTorque().getFriction(); + hasAlphaLongitudinal = CP.getAlphaLongitudinalAvailable(); + hasBSM = CP.getEnableBsm(); + hasDashSpeedLimits = carMake == "ford" || carMake == "hyundai" || carMake == "toyota"; + hasOpenpilotLongitudinal = hasLongitudinalControl(CP); + hasPCMCruise = CP.getPcmCruise(); + hasPedal = CP.getEnableGasInterceptorDEPRECATED(); + hasRadar = !CP.getRadarUnavailable(); + hasSDSU = frogpilot_toggles.value("has_sdsu").toBool(); + hasSNG = CP.getAutoResumeSng(); + hasZSS = frogpilot_toggles.value("has_zss").toBool(); + isAngleCar = CP.getSteerControlType() == cereal::CarParams::SteerControlType::ANGLE; + isBolt = carFingerprint == "CHEVROLET_BOLT_CC" || carFingerprint == "CHEVROLET_BOLT_EUV"; + isGM = carMake == "gm"; + isHKG = carMake == "hyundai"; + isHKGCanFd = isHKG && safetyModel == cereal::CarParams::SafetyModel::HYUNDAI_CANFD; + isSubaru = carMake == "subaru"; + isTorqueCar = CP.getLateralTuning().which() == cereal::CarParams::LateralTuning::TORQUE; + isToyota = carMake == "toyota"; + isTSK = CP.getSecOcRequired(); + isVolt = carFingerprint == "CHEVROLET_VOLT"; + latAccelFactor = CP.getLateralTuning().getTorque().getLatAccelFactor(); + lkasAllowedForAOL = frogpilot_toggles.value("lkas_allowed_for_aol").toBool(); + longitudinalActuatorDelay = CP.getLongitudinalActuatorDelay(); + startAccel = CP.getStartAccel(); + steerActuatorDelay = CP.getSteerActuatorDelay(); + steerKp = 1.0f; + steerRatio = CP.getSteerRatio(); + stopAccel = CP.getStopAccel(); + stoppingDecelRate = CP.getStoppingDecelRate(); + vEgoStarting = CP.getVEgoStarting(); + vEgoStopping = CP.getVEgoStopping(); + + float currentDelayStock = params.getFloat("SteerDelayStock"); + float currentFrictionStock = params.getFloat("SteerFrictionStock"); + float currentKPStock = params.getFloat("SteerKPStock"); + float currentLatAccelStock = params.getFloat("SteerLatAccelStock"); + float currentLongDelayStock = params.getFloat("LongitudinalActuatorDelayStock"); + float currentStartAccelStock = params.getFloat("StartAccelStock"); + float currentSteerRatioStock = params.getFloat("SteerRatioStock"); + float currentStopAccelStock = params.getFloat("StopAccelStock"); + float currentStoppingDecelRateStock = params.getFloat("StoppingDecelRateStock"); + float currentVEgoStartingStock = params.getFloat("VEgoStartingStock"); + float currentVEgoStoppingStock = params.getFloat("VEgoStoppingStock"); + + if (currentDelayStock != steerActuatorDelay && steerActuatorDelay != 0) { + if (params.getFloat("SteerDelay") == currentDelayStock || currentDelayStock == 0) { + params.putFloatNonBlocking("SteerDelay", steerActuatorDelay); + } + params.putFloatNonBlocking("SteerDelayStock", steerActuatorDelay); + } + + if (currentFrictionStock != friction && friction != 0) { + if (params.getFloat("SteerFriction") == currentFrictionStock || currentFrictionStock == 0) { + params.putFloatNonBlocking("SteerFriction", friction); + } + params.putFloatNonBlocking("SteerFrictionStock", friction); + } + + if (currentKPStock != steerKp && steerKp != 0) { + if (params.getFloat("SteerKP") == currentKPStock || currentKPStock == 0) { + params.putFloatNonBlocking("SteerKP", steerKp); + } + params.putFloatNonBlocking("SteerKPStock", steerKp); + } + + if (currentLatAccelStock != latAccelFactor && latAccelFactor != 0) { + if (params.getFloat("SteerLatAccel") == currentLatAccelStock || currentLatAccelStock == 0) { + params.putFloatNonBlocking("SteerLatAccel", latAccelFactor); + } + params.putFloatNonBlocking("SteerLatAccelStock", latAccelFactor); + } + + if (currentLongDelayStock != longitudinalActuatorDelay && longitudinalActuatorDelay != 0) { + if (params.getFloat("LongitudinalActuatorDelay") == currentLongDelayStock || currentLongDelayStock == 0) { + params.putFloatNonBlocking("LongitudinalActuatorDelay", longitudinalActuatorDelay); + } + params.putFloatNonBlocking("LongitudinalActuatorDelayStock", longitudinalActuatorDelay); + } + + if (currentStartAccelStock != startAccel && startAccel != 0) { + if (params.getFloat("StartAccel") == currentStartAccelStock || currentStartAccelStock == 0) { + params.putFloatNonBlocking("StartAccel", startAccel); + } + params.putFloatNonBlocking("StartAccelStock", startAccel); + } + + if (currentSteerRatioStock != steerRatio && steerRatio != 0) { + if (params.getFloat("SteerRatio") == currentSteerRatioStock || currentSteerRatioStock == 0) { + params.putFloatNonBlocking("SteerRatio", steerRatio); + } + params.putFloatNonBlocking("SteerRatioStock", steerRatio); + } + + if (currentStopAccelStock != stopAccel && stopAccel != 0) { + if (params.getFloat("StopAccel") == currentStopAccelStock || currentStopAccelStock == 0) { + params.putFloatNonBlocking("StopAccel", stopAccel); + } + params.putFloatNonBlocking("StopAccelStock", stopAccel); + } + + if (currentStoppingDecelRateStock != stoppingDecelRate && stoppingDecelRate != 0) { + if (params.getFloat("StoppingDecelRate") == currentStoppingDecelRateStock || currentStoppingDecelRateStock == 0) { + params.putFloatNonBlocking("StoppingDecelRate", stoppingDecelRate); + } + params.putFloatNonBlocking("StoppingDecelRateStock", stoppingDecelRate); + } + + if (currentVEgoStartingStock != vEgoStarting && vEgoStarting != 0) { + if (params.getFloat("VEgoStarting") == currentVEgoStartingStock || currentVEgoStartingStock == 0) { + params.putFloatNonBlocking("VEgoStarting", vEgoStarting); + } + params.putFloatNonBlocking("VEgoStartingStock", vEgoStarting); + } + + if (currentVEgoStoppingStock != vEgoStopping && vEgoStopping != 0) { + if (params.getFloat("VEgoStopping") == currentVEgoStoppingStock || currentVEgoStoppingStock == 0) { + params.putFloatNonBlocking("VEgoStopping", vEgoStopping); + } + params.putFloatNonBlocking("VEgoStoppingStock", vEgoStopping); + } + } + + std::string frogpilotCarParams = params.get("FrogPilotCarParamsPersistent"); + if (!frogpilotCarParams.empty()) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader fpcmsg(aligned_buf.align(frogpilotCarParams.data(), frogpilotCarParams.size())); + cereal::FrogPilotCarParams::Reader FPCP = fpcmsg.getRoot(); + + canUsePedal = FPCP.getCanUsePedal(); + canUseSDSU = FPCP.getCanUseSDSU(); + openpilotLongitudinalControlDisabled = FPCP.getOpenpilotLongitudinalControlDisabled(); + } + + std::string liveTorqueParameters = params.get("LiveTorqueParameters"); + if (!liveTorqueParameters.empty()) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader reader(aligned_buf.align(liveTorqueParameters.data(), liveTorqueParameters.size())); + cereal::Event::Reader event = reader.getRoot(); + cereal::LiveTorqueParametersData::Reader LTP = event.getLiveTorqueParameters(); + + hasAutoTune = LTP.getUseParams(); + } + + drivingPanelButtons->setVisibleButton(0, tuningLevel >= frogpilotToggleLevels.value("DrivingModel").toDouble()); + drivingPanelButtons->setVisibleButton(1, hasOpenpilotLongitudinal); + + systemPanelButtons->setVisibleButton(1, tuningLevel >= frogpilotToggleLevels.value("DeviceManagement").toDouble() || tuningLevel >= frogpilotToggleLevels.value("ScreenManagement").toDouble()); + + vehiclePanelButtons->setVisibleButton(1, tuningLevel >= frogpilotToggleLevels.value("WheelControls").toDouble()); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/frogpilot_settings.h b/frogpilot/ui/qt/offroad/frogpilot_settings.h new file mode 100644 index 000000000..a8b5927f3 --- /dev/null +++ b/frogpilot/ui/qt/offroad/frogpilot_settings.h @@ -0,0 +1,103 @@ +#pragma once + +#include "selfdrive/ui/qt/offroad/settings.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +#include "frogpilot/ui/frogpilot_ui.h" +#include "frogpilot/ui/qt/widgets/frogpilot_controls.h" + +class QNetworkAccessManager; + +class FrogPilotSettingsWindow : public QFrame { + Q_OBJECT + +public: + explicit FrogPilotSettingsWindow(SettingsWindow *parent); + + void updateVariables(); + + bool canUsePedal = false; + bool canUseSDSU = false; + bool forceOpenDescriptions = false; + bool hasAlphaLongitudinal = false; + bool hasAutoTune = true; + bool hasBSM = true; + bool hasDashSpeedLimits = true; + bool hasNNFFLog = true; + bool hasOpenpilotLongitudinal = true; + bool hasPCMCruise = false; + bool hasPedal = false; + bool hasRadar = true; + bool hasSDSU = false; + bool hasSNG = false; + bool hasZSS = false; + bool isAngleCar = false; + bool isBolt = false; + bool isFrogsGoMoo = false; + bool isGM = true; + bool isHKG = true; + bool isHKGCanFd = true; + bool isSubaru = false; + bool isTorqueCar = false; + bool isToyota = true; + bool isTSK = false; + bool isVolt = true; + bool keepScreenOn = false; + bool lkasAllowedForAOL = false; + bool openpilotLongitudinalControlDisabled = false; + + float friction; + float latAccelFactor; + float longitudinalActuatorDelay; + float startAccel; + float steerActuatorDelay; + float steerKp; + float steerRatio; + float stopAccel; + float stoppingDecelRate; + float vEgoStarting; + float vEgoStopping; + + int tuningLevel; + + QJsonObject frogpilotToggleLevels; + +signals: + void closeSubPanel(); + void closeSubSubPanel(); + void closeSubSubSubPanel(); + void openPanel(); + void openSubPanel(); + void openSubSubPanel(); + void openSubSubSubPanel(); + void tuningLevelChanged(int level); + void updateMetric(bool metric, bool bootRun=false); + +private: + void closePanel(); + void createPanelButtons(FrogPilotListWidget *list); + void hideEvent(QHideEvent *event) override; + void showEvent(QShowEvent *event) override; + void updateState(); + void updateTuningLevel(); + + bool panelOpen; + + std::string carMake; + + FrogPilotButtonsControl *drivingPanelButtons; + FrogPilotButtonsControl *navigationPanelButtons; + FrogPilotButtonsControl *soundPanelButtons; + FrogPilotButtonsControl *systemPanelButtons; + FrogPilotButtonsControl *themePanelButtons; + FrogPilotButtonsControl *togglePreset; + FrogPilotButtonsControl *vehiclePanelButtons; + + Params params; + + QJsonObject shownDescriptions; + + QStackedLayout *mainLayout; + + ScrollView *frogpilotPanel; +}; diff --git a/frogpilot/ui/qt/offroad/lateral_settings.cc b/frogpilot/ui/qt/offroad/lateral_settings.cc new file mode 100644 index 000000000..2430e46f2 --- /dev/null +++ b/frogpilot/ui/qt/offroad/lateral_settings.cc @@ -0,0 +1,431 @@ +#include "frogpilot/ui/qt/offroad/lateral_settings.h" + +FrogPilotLateralPanel::FrogPilotLateralPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + QStackedLayout *lateralLayout = new QStackedLayout(); + addItem(lateralLayout); + + FrogPilotListWidget *lateralList = new FrogPilotListWidget(this); + + ScrollView *lateralPanel = new ScrollView(lateralList, this); + + lateralLayout->addWidget(lateralPanel); + + FrogPilotListWidget *advancedLateralTuneList = new FrogPilotListWidget(this); + FrogPilotListWidget *aolList = new FrogPilotListWidget(this); + FrogPilotListWidget *laneChangeList = new FrogPilotListWidget(this); + FrogPilotListWidget *lateralTuneList = new FrogPilotListWidget(this); + FrogPilotListWidget *qolList = new FrogPilotListWidget(this); + + ScrollView *advancedLateralTunePanel = new ScrollView(advancedLateralTuneList, this); + ScrollView *aolPanel = new ScrollView(aolList, this); + ScrollView *laneChangePanel = new ScrollView(laneChangeList, this); + ScrollView *lateralTunePanel = new ScrollView(lateralTuneList, this); + ScrollView *qolPanel = new ScrollView(qolList, this); + + lateralLayout->addWidget(advancedLateralTunePanel); + lateralLayout->addWidget(aolPanel); + lateralLayout->addWidget(laneChangePanel); + lateralLayout->addWidget(lateralTunePanel); + lateralLayout->addWidget(qolPanel); + + const std::vector> lateralToggles { + {"AdvancedLateralTune", tr("Advanced Lateral Tuning"), tr("Advanced steering control changes to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_advanced_lateral_tune.png"}, + {"SteerDelay", parent->steerActuatorDelay != 0 ? QString(tr("Actuator Delay (Default: %1)")).arg(QString::number(parent->steerActuatorDelay, 'f', 2)) : tr("Actuator Delay"), tr("The time between openpilot's steering command and the vehicle's response. Increase if the vehicle reacts late; decrease if it feels jumpy. Auto-learned by default."), ""}, + {"SteerFriction", parent->friction != 0 ? QString(tr("Friction (Default: %1)")).arg(QString::number(parent->friction, 'f', 2)) : tr("Friction"), tr("Compensates for steering friction. Increase if the wheel sticks near center; decrease if it jitters. Auto-learned by default."), ""}, + {"SteerKP", parent->steerKp != 0 ? QString(tr("Kp Factor (Default: %1)")).arg(QString::number(parent->steerKp, 'f', 2)) : tr("Kp Factor"), tr("How strongly openpilot corrects lane position. Higher is tighter but twitchier; lower is smoother but slower. Auto-learned by default."), ""}, + {"SteerLatAccel", parent->latAccelFactor != 0 ? QString(tr("Lateral Acceleration (Default: %1)")).arg(QString::number(parent->latAccelFactor, 'f', 2)) : tr("Lateral Acceleration"), tr("Maps steering torque to turning response. Increase for sharper turns; decrease for gentler steering. Auto-learned by default."), ""}, + {"SteerRatio", parent->steerRatio != 0 ? QString(tr("Steer Ratio (Default: %1)")).arg(QString::number(parent->steerRatio, 'f', 2)) : tr("Steer Ratio"), tr("The relationship between steering wheel rotation and road wheel angle. Increase if steering feels too quick or twitchy; decrease if it feels too slow or weak. Auto-learned by default."), ""}, + {"ForceAutoTune", tr("Force Auto-Tune On"), tr("Force-enable openpilot's live auto-tuning for \"Friction\" and \"Lateral Acceleration\"."), ""}, + {"ForceAutoTuneOff", tr("Force Auto-Tune Off"), tr("Force-disable openpilot's live auto-tuning for \"Friction\" and \"Lateral Acceleration\" and use the set value instead."), ""}, + {"ForceTorqueController", tr("Force Torque Controller"), tr("Use torque-based steering control instead of angle-based control for smoother lane keeping, especially in curves."), ""}, + + {"AlwaysOnLateral", tr("Always On Lateral"), tr("openpilot's steering remains active even when the accelerator or brake pedals are pressed."), "../../frogpilot/assets/toggle_icons/icon_always_on_lateral.png"}, + {"AlwaysOnLateralLKAS", tr("Enable With LKAS"), tr("Enable \"Always On Lateral\" whenever \"LKAS\" is on, even when openpilot is not engaged."), ""}, + {"PauseAOLOnBrake", tr("Pause on Brake Press Below"), tr("Pause \"Always On Lateral\" below the set speed while the brake pedal is pressed."), ""}, + + {"LaneChanges", tr("Lane Changes"), tr("Allow openpilot to change lanes."), "../../frogpilot/assets/toggle_icons/icon_lane.png"}, + {"NudgelessLaneChange", tr("Automatic Lane Changes"), tr("When the turn signal is on, openpilot will automatically change lanes. No steering-wheel nudge required!"), ""}, + {"LaneChangeTime", tr("Lane Change Delay"), tr("Delay between turn signal activation and the start of an automatic lane change."), ""}, + {"MinimumLaneChangeSpeed", tr("Minimum Lane Change Speed"), tr("Lowest speed at which openpilot will change lanes."), ""}, + {"LaneDetectionWidth", tr("Minimum Lane Width"), tr("Prevent automatic lane changes into lanes narrower than the set width."), ""}, + {"OneLaneChange", tr("One Lane Change Per Signal"), tr("Limit automatic lane changes to one per turn-signal activation."), ""}, + + {"LateralTune", tr("Lateral Tuning"), tr("Miscellaneous steering control changes to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_lateral_tune.png"}, + {"TurnDesires", tr("Force Turn Desires Below Lane Change Speed"), tr("While driving below the minimum lane change speed with an active turn signal, instruct openpilot to turn left/right."), ""}, + {"NNFF", tr("Neural Network Feedforward (NNFF)"), tr("Twilsonco's \"Neural Network FeedForward\" controller. Uses a trained neural network model to predict steering torque based on vehicle speed, roll, and past/future planned path data for smoother, model-based steering."), ""}, + {"NNFFLite", tr("Neural Network Feedforward (NNFF) Lite"), tr("A lightweight version of Twilsonco's \"Neural Network FeedForward\" controller. Uses the \"look-ahead\" planned lateral jerk logic from the full model to help smoothen steering adjustments in curves, but does not use the full neural network for torque calculation."), ""}, + + {"QOLLateral", tr("Quality of Life"), tr("Steering control changes to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_quality_of_life.png"}, + {"PauseLateralSpeed", tr("Pause Steering Below"), tr("Pause steering below the set speed."), ""}, + + {"IgnoreMe", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""}, + {"IgnoreMe2", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""} + }; + + for (const auto &[param, title, desc, icon] : lateralToggles) { + AbstractControl *lateralToggle; + + if (param == "AdvancedLateralTune") { + FrogPilotManageControl *advancedLateralTuneToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(advancedLateralTuneToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, advancedLateralTunePanel]() { + lateralLayout->setCurrentWidget(advancedLateralTunePanel); + }); + lateralToggle = advancedLateralTuneToggle; + } else if (param == "SteerDelay") { + std::vector steerDelayButton{"Reset"}; + lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0.01, 1, QString(), std::map(), 0.01, false, {}, steerDelayButton, false, false); + } else if (param == "SteerFriction") { + std::vector steerFrictionButton{"Reset"}; + lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0, 1, QString(), std::map(), 0.01, false, {}, steerFrictionButton, false, false); + } else if (param == "SteerKP") { + std::vector steerKPButton{"Reset"}; + lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, parent->steerKp * 0.5, parent->steerKp * 1.5, QString(), std::map(), 0.01, false, {}, steerKPButton, false, false); + } else if (param == "SteerLatAccel") { + std::vector steerLatAccelButton{"Reset"}; + lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, parent->latAccelFactor * 0.5, parent->latAccelFactor * 1.5, QString(), std::map(), 0.01, false, {}, steerLatAccelButton, false, false); + } else if (param == "SteerRatio") { + std::vector steerRatioButton{"Reset"}; + lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, parent->steerRatio * 0.5, parent->steerRatio * 1.5, QString(), std::map(), 0.01, false, {}, steerRatioButton, false, false); + + } else if (param == "AlwaysOnLateral") { + FrogPilotManageControl *aolToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(aolToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, aolPanel]() { + lateralLayout->setCurrentWidget(aolPanel); + }); + lateralToggle = aolToggle; + } else if (param == "PauseAOLOnBrake") { + lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, QString(), std::map(), 1, true); + + } else if (param == "LaneChanges") { + FrogPilotManageControl *laneChangeToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(laneChangeToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, laneChangePanel]() { + lateralLayout->setCurrentWidget(laneChangePanel); + }); + lateralToggle = laneChangeToggle; + } else if (param == "LaneChangeTime") { + std::map laneChangeTimeLabels; + for (float i = 0; i <= 5; i += 0.1) { + laneChangeTimeLabels[i] = i == 0 ? tr("Instant") : std::lround(i / 0.1) == 1 / 0.1 ? QString::number(i, 'f', 1) + tr(" second") : QString::number(i, 'f', 1) + tr(" seconds"); + } + lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 5, QString(), laneChangeTimeLabels, 0.1); + } else if (param == "LaneDetectionWidth") { + lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 15, QString(), std::map(), 0.1, true); + } else if (param == "MinimumLaneChangeSpeed") { + lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, QString(), std::map(), 1, true); + + } else if (param == "LateralTune") { + FrogPilotManageControl *lateralTuneToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(lateralTuneToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, lateralTunePanel]() { + lateralLayout->setCurrentWidget(lateralTunePanel); + }); + lateralToggle = lateralTuneToggle; + + } else if (param == "QOLLateral") { + FrogPilotManageControl *qolLateralToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(qolLateralToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, qolPanel]() { + lateralLayout->setCurrentWidget(qolPanel); + }); + lateralToggle = qolLateralToggle; + } else if (param == "PauseLateralSpeed") { + std::vector pauseLateralToggles{"PauseLateralOnSignal"}; + std::vector pauseLateralToggleNames{tr("Turn Signal Only")}; + lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0, 99, QString(), std::map(), 1, true, pauseLateralToggles, pauseLateralToggleNames, true); + + } else { + lateralToggle = new ParamControl(param, title, desc, icon); + } + + toggles[param] = lateralToggle; + + if (advancedLateralTuneKeys.contains(param)) { + advancedLateralTuneList->addItem(lateralToggle); + } else if (aolKeys.contains(param)) { + aolList->addItem(lateralToggle); + } else if (laneChangeKeys.contains(param)) { + laneChangeList->addItem(lateralToggle); + } else if (lateralTuneKeys.contains(param)) { + lateralTuneList->addItem(lateralToggle); + } else if (qolKeys.contains(param)) { + qolList ->addItem(lateralToggle); + } else { + lateralList->addItem(lateralToggle); + + parentKeys.insert(param); + } + + if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast(lateralToggle)) { + QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { + emit openSubPanel(); + openDescriptions(forceOpenDescriptions, toggles); + }); + } + + QObject::connect(lateralToggle, &AbstractControl::hideDescriptionEvent, [this]() { + update(); + }); + QObject::connect(lateralToggle, &AbstractControl::showDescriptionEvent, [this]() { + update(); + }); + } + + QSet forceUpdateKeys = {"ForceAutoTune", "ForceAutoTuneOff", "LateralTune", "NNFF", "NudgelessLaneChange"}; + for (const QString &key : forceUpdateKeys) { + QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, this, &FrogPilotLateralPanel::updateToggles); + } + + QSet rebootKeys = {"AlwaysOnLateral", "ForceTorqueController", "NNFF", "NNFFLite"}; + for (const QString &key : rebootKeys) { + QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) { + if (started) { + if (key == "AlwaysOnLateral" && state) { + if (FrogPilotConfirmationDialog::toggleReboot(this)) { + Hardware::reboot(); + } + } else if (key != "AlwaysOnLateral") { + if (FrogPilotConfirmationDialog::toggleReboot(this)) { + Hardware::reboot(); + } + } + } + }); + } + + steerDelayToggle = static_cast(toggles["SteerDelay"]); + QObject::connect(steerDelayToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Reset Actuator Delay to its default value?"), this)) { + params.putFloat("SteerDelay", parent->steerActuatorDelay); + steerDelayToggle->refresh(); + } + }); + + steerFrictionToggle = static_cast(toggles["SteerFriction"]); + QObject::connect(steerFrictionToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Reset Friction to its default value?"), this)) { + params.putFloat("SteerFriction", parent->friction); + steerFrictionToggle->refresh(); + } + }); + + steerKPToggle = static_cast(toggles["SteerKP"]); + QObject::connect(steerKPToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Reset Kp Factor to its default value?"), this)) { + params.putFloat("SteerKP", parent->steerKp); + steerKPToggle->refresh(); + } + }); + + steerLatAccelToggle = static_cast(toggles["SteerLatAccel"]); + QObject::connect(steerLatAccelToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Reset Lateral Accel to its default value?"), this)) { + params.putFloat("SteerLatAccel", parent->latAccelFactor); + steerLatAccelToggle->refresh(); + } + }); + + steerRatioToggle = static_cast(toggles["SteerRatio"]); + QObject::connect(steerRatioToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Reset Steer Ratio to its default value?"), this)) { + params.putFloat("SteerRatio", parent->steerRatio); + steerRatioToggle->refresh(); + } + }); + + openDescriptions(forceOpenDescriptions, toggles); + + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [lateralLayout, lateralPanel, this] { + openDescriptions(forceOpenDescriptions, toggles); + lateralLayout->setCurrentWidget(lateralPanel); + }); + QObject::connect(parent, &FrogPilotSettingsWindow::updateMetric, this, &FrogPilotLateralPanel::updateMetric); + QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotLateralPanel::updateState); +} + +void FrogPilotLateralPanel::showEvent(QShowEvent *event) { + steerDelayToggle->setTitle(QString(tr("Actuator Delay (Default: %1)")).arg(QString::number(parent->steerActuatorDelay, 'f', 2))); + steerFrictionToggle->setTitle(QString(tr("Friction (Default: %1)")).arg(QString::number(parent->friction, 'f', 2))); + steerKPToggle->setTitle(QString(tr("Kp Factor (Default: %1)")).arg(QString::number(parent->steerKp, 'f', 2))); + steerKPToggle->updateControl(parent->steerKp * 0.5, parent->steerKp * 1.5); + steerLatAccelToggle->setTitle(QString(tr("Lateral Accel (Default: %1)")).arg(QString::number(parent->latAccelFactor, 'f', 2))); + steerLatAccelToggle->updateControl(parent->latAccelFactor * 0.5, parent->latAccelFactor * 1.5); + steerRatioToggle->setTitle(QString(tr("Steer Ratio (Default: %1)")).arg(QString::number(parent->steerRatio, 'f', 2))); + steerRatioToggle->updateControl(parent->steerRatio * 0.5, parent->steerRatio * 1.5); + + updateToggles(); +} + +void FrogPilotLateralPanel::updateState(const UIState &s) { + if (!isVisible()) return; + + started = s.scene.started; +} + +void FrogPilotLateralPanel::updateMetric(bool metric, bool bootRun) { + static bool previousMetric; + if (metric != previousMetric && !bootRun) { + double distanceConversion = metric ? FOOT_TO_METER : METER_TO_FOOT; + double speedConversion = metric ? MILE_TO_KM : KM_TO_MILE; + + params.putFloatNonBlocking("LaneDetectionWidth", params.getFloat("LaneDetectionWidth") * distanceConversion); + + params.putIntNonBlocking("MinimumLaneChangeSpeed", params.getInt("MinimumLaneChangeSpeed") * speedConversion); + params.putIntNonBlocking("PauseAOLOnBrake", params.getInt("PauseAOLOnBrake") * speedConversion); + params.putIntNonBlocking("PauseLateralSpeed", params.getInt("PauseLateralSpeed") * speedConversion); + } + previousMetric = metric; + + static std::map imperialDistanceLabels; + static std::map imperialSpeedLabels; + static std::map metricDistanceLabels; + static std::map metricSpeedLabels; + + static bool labelsInitialized = false; + if (!labelsInitialized) { + for (int i = 0; i <= 150; ++i) { + float key = i / 10.0f; + imperialDistanceLabels[key] = key == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" foot") : QString::number(key, 'f', 1) + tr(" feet"); + } + + for (int i = 0; i <= 99; ++i) { + imperialSpeedLabels[i] = i == 0 ? tr("Off") : QString::number(i) + tr(" mph"); + } + + for (int i = 0; i <= 50; ++i) { + float key = i / 10.0f; + metricDistanceLabels[key] = key == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" meter") : QString::number(key, 'f', 1) + tr(" meters"); + } + + for (int i = 0; i <= 150; ++i) { + metricSpeedLabels[i] = i == 0 ? tr("Off") : QString::number(i) + tr(" km/h"); + } + + labelsInitialized = true; + } + + FrogPilotParamValueControl *laneWidthToggle = static_cast(toggles["LaneDetectionWidth"]); + FrogPilotParamValueControl *minimumLaneChangeSpeedToggle = static_cast(toggles["MinimumLaneChangeSpeed"]); + FrogPilotParamValueControl *pauseAOLOnBrakeToggle = static_cast(toggles["PauseAOLOnBrake"]); + FrogPilotParamValueControl *pauseLateralToggle = static_cast(toggles["PauseLateralSpeed"]); + + if (metric) { + laneWidthToggle->updateControl(0, 5, metricDistanceLabels); + + minimumLaneChangeSpeedToggle->updateControl(0, 150, metricSpeedLabels); + pauseAOLOnBrakeToggle->updateControl(0, 150, metricSpeedLabels); + pauseLateralToggle->updateControl(0, 150, metricSpeedLabels); + } else { + laneWidthToggle->updateControl(0, 15, imperialDistanceLabels); + + minimumLaneChangeSpeedToggle->updateControl(0, 99, imperialSpeedLabels); + pauseAOLOnBrakeToggle->updateControl(0, 99, imperialSpeedLabels); + pauseLateralToggle->updateControl(0, 99, imperialSpeedLabels); + } +} + +void FrogPilotLateralPanel::updateToggles() { + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + toggle->setVisible(false); + } + } + + bool forcingAutoTune = !parent->hasAutoTune && params.getBool("ForceAutoTune"); + bool forcingAutoTuneOff = parent->hasAutoTune && params.getBool("ForceAutoTuneOff"); + bool forcingTorqueController = !parent->isAngleCar && params.getBool("ForceTorqueController"); + bool usingNNFF = parent->hasNNFFLog && params.getBool("LateralTune") && params.getBool("NNFF"); + + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + continue; + } + + bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble(); + + if (key == "AlwaysOnLateralLKAS") { + setVisible &= parent->lkasAllowedForAOL; + } + + else if (key == "ForceAutoTune") { + setVisible &= !parent->hasAutoTune; + setVisible &= !parent->isAngleCar; + setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF; + } + + else if (key == "ForceAutoTuneOff") { + setVisible &= parent->hasAutoTune; + } + + else if (key == "ForceTorqueController") { + setVisible &= !parent->isAngleCar; + setVisible &= !parent->isTorqueCar; + } + + else if (key == "LaneChangeTime") { + setVisible &= params.getBool("LaneChanges") && params.getBool("NudgelessLaneChange"); + } + + else if (key == "LaneDetectionWidth") { + setVisible &= params.getBool("LaneChanges") && params.getBool("NudgelessLaneChange"); + } + + else if (key == "NNFF") { + setVisible &= parent->hasNNFFLog; + setVisible &= !parent->isAngleCar; + } + + else if (key == "NNFFLite") { + setVisible &= !usingNNFF; + setVisible &= !parent->isAngleCar; + } + + else if (key == "SteerDelay") { + setVisible &= parent->steerActuatorDelay != 0; + } + + else if (key == "SteerFriction") { + setVisible &= parent->friction != 0; + setVisible &= parent->hasAutoTune ? forcingAutoTuneOff : !forcingAutoTune; + setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF; + setVisible &= !usingNNFF; + } + + else if (key == "SteerKP") { + setVisible &= parent->steerKp != 0; + setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF; + setVisible &= !parent->isAngleCar; + } + + else if (key == "SteerLatAccel") { + setVisible &= parent->latAccelFactor != 0; + setVisible &= parent->hasAutoTune ? forcingAutoTuneOff : !forcingAutoTune; + setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF; + setVisible &= !usingNNFF; + } + + else if (key == "SteerRatio") { + setVisible &= parent->steerRatio != 0; + setVisible &= parent->hasAutoTune ? forcingAutoTuneOff : !forcingAutoTune; + } + + toggle->setVisible(setVisible); + + if (setVisible) { + if (advancedLateralTuneKeys.contains(key)) { + toggles["AdvancedLateralTune"]->setVisible(true); + } else if (aolKeys.contains(key)) { + toggles["AlwaysOnLateral"]->setVisible(true); + } else if (laneChangeKeys.contains(key)) { + toggles["LaneChanges"]->setVisible(true); + } else if (lateralTuneKeys.contains(key)) { + toggles["LateralTune"]->setVisible(true); + } else if (qolKeys.contains(key)) { + toggles["QOLLateral"]->setVisible(true); + } + } + } + + openDescriptions(forceOpenDescriptions, toggles); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/lateral_settings.h b/frogpilot/ui/qt/offroad/lateral_settings.h new file mode 100644 index 000000000..08117e377 --- /dev/null +++ b/frogpilot/ui/qt/offroad/lateral_settings.h @@ -0,0 +1,44 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotLateralPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotLateralPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +signals: + void openSubPanel(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void updateMetric(bool metric, bool bootRun); + void updateState(const UIState &s); + void updateToggles(); + + bool forceOpenDescriptions; + bool started; + + std::map toggles; + + QSet advancedLateralTuneKeys = {"ForceAutoTune", "ForceAutoTuneOff", "ForceTorqueController", "SteerDelay", "SteerFriction", "SteerLatAccel", "SteerKP", "SteerRatio"}; + QSet aolKeys = {"AlwaysOnLateralLKAS", "PauseAOLOnBrake"}; + QSet laneChangeKeys = {"LaneChangeTime", "LaneDetectionWidth", "MinimumLaneChangeSpeed", "NudgelessLaneChange", "OneLaneChange"}; + QSet lateralTuneKeys = {"NNFF", "NNFFLite", "TurnDesires"}; + QSet qolKeys = {"PauseLateralSpeed"}; + + QSet parentKeys; + + FrogPilotParamValueButtonControl *steerDelayToggle; + FrogPilotParamValueButtonControl *steerFrictionToggle; + FrogPilotParamValueButtonControl *steerLatAccelToggle; + FrogPilotParamValueButtonControl *steerKPToggle; + FrogPilotParamValueButtonControl *steerRatioToggle; + + FrogPilotSettingsWindow *parent; + + Params params; +}; diff --git a/frogpilot/ui/qt/offroad/longitudinal_settings.cc b/frogpilot/ui/qt/offroad/longitudinal_settings.cc new file mode 100644 index 000000000..354e4bdaf --- /dev/null +++ b/frogpilot/ui/qt/offroad/longitudinal_settings.cc @@ -0,0 +1,1085 @@ +#include "frogpilot/ui/qt/offroad/longitudinal_settings.h" + +FrogPilotLongitudinalPanel::FrogPilotLongitudinalPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + networkManager = new QNetworkAccessManager(this); + + QStackedLayout *longitudinalLayout = new QStackedLayout(); + addItem(longitudinalLayout); + + FrogPilotListWidget *longitudinalList = new FrogPilotListWidget(this); + + ScrollView *longitudinalPanel = new ScrollView(longitudinalList, this); + + longitudinalLayout->addWidget(longitudinalPanel); + + FrogPilotListWidget *advancedLongitudinalTuneList = new FrogPilotListWidget(this); + FrogPilotListWidget *aggressivePersonalityList = new FrogPilotListWidget(this); + FrogPilotListWidget *conditionalExperimentalList = new FrogPilotListWidget(this); + FrogPilotListWidget *curveSpeedList = new FrogPilotListWidget(this); + FrogPilotListWidget *customDrivingPersonalityList = new FrogPilotListWidget(this); + FrogPilotListWidget *longitudinalTuneList = new FrogPilotListWidget(this); + FrogPilotListWidget *qolList = new FrogPilotListWidget(this); + FrogPilotListWidget *relaxedPersonalityList = new FrogPilotListWidget(this); + FrogPilotListWidget *speedLimitControllerList = new FrogPilotListWidget(this); + FrogPilotListWidget *speedLimitControllerOffsetsList = new FrogPilotListWidget(this); + FrogPilotListWidget *speedLimitControllerQOLList = new FrogPilotListWidget(this); + FrogPilotListWidget *speedLimitControllerVisualList = new FrogPilotListWidget(this); + FrogPilotListWidget *standardPersonalityList = new FrogPilotListWidget(this); + FrogPilotListWidget *trafficPersonalityList = new FrogPilotListWidget(this); + FrogPilotListWidget *weatherList = new FrogPilotListWidget(this); + FrogPilotListWidget *weatherLowVisibilityList = new FrogPilotListWidget(this); + FrogPilotListWidget *weatherRainList = new FrogPilotListWidget(this); + FrogPilotListWidget *weatherRainStormList = new FrogPilotListWidget(this); + FrogPilotListWidget *weatherSnowList = new FrogPilotListWidget(this); + + ScrollView *advancedLongitudinalTunePanel = new ScrollView(advancedLongitudinalTuneList, this); + ScrollView *aggressivePersonalityPanel = new ScrollView(aggressivePersonalityList, this); + ScrollView *conditionalExperimentalPanel = new ScrollView(conditionalExperimentalList, this); + ScrollView *curveSpeedPanel = new ScrollView(curveSpeedList, this); + ScrollView *customDrivingPersonalityPanel = new ScrollView(customDrivingPersonalityList, this); + ScrollView *longitudinalTunePanel = new ScrollView(longitudinalTuneList, this); + ScrollView *qolPanel = new ScrollView(qolList, this); + ScrollView *relaxedPersonalityPanel = new ScrollView(relaxedPersonalityList, this); + ScrollView *speedLimitControllerPanel = new ScrollView(speedLimitControllerList, this); + ScrollView *speedLimitControllerOffsetsPanel = new ScrollView(speedLimitControllerOffsetsList, this); + ScrollView *speedLimitControllerQOLPanel = new ScrollView(speedLimitControllerQOLList, this); + ScrollView *speedLimitControllerVisualPanel = new ScrollView(speedLimitControllerVisualList, this); + ScrollView *standardPersonalityPanel = new ScrollView(standardPersonalityList, this); + ScrollView *trafficPersonalityPanel = new ScrollView(trafficPersonalityList, this); + ScrollView *weatherLowVisibilityPanel = new ScrollView(weatherLowVisibilityList, this); + ScrollView *weatherPanel = new ScrollView(weatherList, this); + ScrollView *weatherRainPanel = new ScrollView(weatherRainList, this); + ScrollView *weatherRainStormPanel = new ScrollView(weatherRainStormList, this); + ScrollView *weatherSnowPanel = new ScrollView(weatherSnowList, this); + + longitudinalLayout->addWidget(advancedLongitudinalTunePanel); + longitudinalLayout->addWidget(aggressivePersonalityPanel); + longitudinalLayout->addWidget(conditionalExperimentalPanel); + longitudinalLayout->addWidget(curveSpeedPanel); + longitudinalLayout->addWidget(customDrivingPersonalityPanel); + longitudinalLayout->addWidget(longitudinalTunePanel); + longitudinalLayout->addWidget(qolPanel); + longitudinalLayout->addWidget(relaxedPersonalityPanel); + longitudinalLayout->addWidget(speedLimitControllerPanel); + longitudinalLayout->addWidget(speedLimitControllerOffsetsPanel); + longitudinalLayout->addWidget(speedLimitControllerQOLPanel); + longitudinalLayout->addWidget(speedLimitControllerVisualPanel); + longitudinalLayout->addWidget(standardPersonalityPanel); + longitudinalLayout->addWidget(trafficPersonalityPanel); + longitudinalLayout->addWidget(weatherLowVisibilityPanel); + longitudinalLayout->addWidget(weatherPanel); + longitudinalLayout->addWidget(weatherRainPanel); + longitudinalLayout->addWidget(weatherRainStormPanel); + longitudinalLayout->addWidget(weatherSnowPanel); + + const std::vector> longitudinalToggles { + {"AdvancedLongitudinalTune", tr("Advanced Longitudinal Tuning"), tr("Advanced acceleration and braking control changes to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_advanced_longitudinal_tune.png"}, + {"LongitudinalActuatorDelay", parent->longitudinalActuatorDelay != 0 ? QString(tr("Actuator Delay (Default: %1)")).arg(QString::number(parent->longitudinalActuatorDelay, 'f', 2)) : tr("Actuator Delay"), tr("The time between openpilot's throttle or brake command and the vehicle's response. Increase if the vehicle feels slow to react; decrease if it feels too eager or overshoots."), ""}, + {"MaxDesiredAcceleration", tr("Maximum Acceleration"), tr("Limit the strongest acceleration openpilot can command."), ""}, + {"StartAccel", parent->startAccel != 0 ? QString(tr("Start Acceleration (Default: %1)")).arg(QString::number(parent->startAccel, 'f', 2)) : tr("Start Acceleration"), tr("Extra acceleration applied when starting from a stop. Increase for quicker takeoffs; decrease for smoother, gentler starts."), ""}, + {"VEgoStarting", parent->vEgoStarting != 0 ? QString(tr("Start Speed (Default: %1)")).arg(QString::number(parent->vEgoStarting, 'f', 2)) : tr("Start Speed"), tr("The speed at which openpilot exits the stopped state. Increase to reduce creeping; decrease to move sooner after stopping."), ""}, + {"StopAccel", parent->stopAccel != 0 ? QString(tr("Stop Acceleration (Default: %1)")).arg(QString::number(parent->stopAccel, 'f', 2)) : tr("Stop Acceleration"), tr("Brake force applied to hold the vehicle at a standstill. Increase to prevent rolling on hills; decrease for smoother, softer stops."), ""}, + {"StoppingDecelRate", parent->stoppingDecelRate != 0 ? QString(tr("Stopping Rate (Default: %1)")).arg(QString::number(parent->stoppingDecelRate, 'f', 2)) : tr("Stopping Rate"), tr("How quickly braking ramps up when stopping. Increase for shorter, firmer stops; decrease for smoother, longer stops."), ""}, + {"VEgoStopping", parent->vEgoStopping != 0 ? QString(tr("Stop Speed (Default: %1)")).arg(QString::number(parent->vEgoStopping, 'f', 2)) : tr("Stop Speed"), tr("The speed at which openpilot considers the vehicle stopped. Increase to brake earlier and stop smoothly; decrease to wait longer but risk overshooting."), ""}, + + {"ConditionalExperimental", tr("Conditional Experimental Mode"), tr("Automatically switch to \"Experimental Mode\" when set conditions are met. Allows the model to handle challenging situations with smarter decision making."), "../../frogpilot/assets/toggle_icons/icon_conditional.png"}, + {"CESpeed", tr("Below"), tr("Switch to \"Experimental Mode\" when driving below this speed without a lead to help openpilot handle low-speed situations more smoothly."), ""}, + {"CECurves", tr("Curve Detected Ahead"), tr("Switch to \"Experimental Mode\" when a curve is detected to allow the model to set an appropriate speed for the curve."), ""}, + {"CEStopLights", tr("\"Detected\" Stop Lights/Signs"), tr("Switch to \"Experimental Mode\" whenever the driving model \"detects\" a red light or stop sign.

Disclaimer: openpilot does not explicitly detect traffic lights or stop signs. In \"Experimental Mode\", openpilot makes end-to-end driving decisions from camera input, which means it may stop even when there's no clear reason!"), ""}, + {"CELead", tr("Lead Detected Ahead"), tr("Switch to \"Experimental Mode\" when a slower or stopped vehicle is detected. Can make braking smoother and more reliable on some vehicles."), ""}, + {"CEModelStopTime", tr("Predicted Stop In"), tr("Switch to \"Experimental Mode\" when openpilot predicts a stop within the set time. This is usually triggered when the model \"sees\" a red light or stop sign ahead.

Disclaimer: openpilot does not explicitly detect traffic lights or stop signs. In \"Experimental Mode\", openpilot makes end-to-end driving decisions from camera input, which means it may stop even when there's no clear reason!"), ""}, + {"CESignalSpeed", tr("Turn Signal Below"), tr("Switch to \"Experimental Mode\" when using a turn signal below the set speed to allow the model to choose an appropriate speed for smoother left and right turns."), ""}, + {"ShowCEMStatus", tr("Status Widget"), tr("Show which condition triggered \"Experimental Mode\" on the driving screen."), ""}, + + {"CurveSpeedController", tr("Curve Speed Controller"), tr("Automatically slow down for upcoming curves using data learned from your driving style, adapting to curves as you would."), "../../frogpilot/assets/toggle_icons/icon_speed_map.png"}, + {"CalibratedLateralAcceleration", tr("Calibrated Lateral Acceleration"), tr("The learned lateral acceleration from collected driving data. This sets how fast openpilot will take curves. Higher values allow faster cornering; lower values slow the vehicle for gentler turns."), ""}, + {"CalibrationProgress", tr("Calibration Progress"), tr("How much curve data has been collected. This is a progress meter; it is normal for the value to stay low and rarely reach 100%."), ""}, + {"ResetCurveData", tr("Reset Curve Data"), tr("Reset collected user data for \"Curve Speed Controller\"."), ""}, + {"ShowCSCStatus", tr("Status Widget"), tr("Show the \"Curve Speed Controller\" target speed on the driving screen."), ""}, + + {"CustomPersonalities", tr("Driving Personalities"), tr("Customize the \"Driving Personalities\" to better match your driving style."), "../../frogpilot/assets/toggle_icons/icon_personality.png"}, + + {"TrafficPersonalityProfile", tr("Traffic Mode"), tr("Customize the \"Traffic Mode\" personality profile. Designed for stop-and-go driving."), "../../frogpilot/assets/stock_theme/distance_icons/traffic.png"}, + {"TrafficFollow", tr("Following Distance"), tr("The minimum following distance to the lead vehicle in \"Traffic Mode\". openpilot blends between this value and the \"Aggressive\" profile as speed increases. Increase for more space; decrease for tighter gaps."), ""}, + {"TrafficJerkAcceleration", tr("Acceleration Smoothness"), tr("How smoothly openpilot accelerates in \"Traffic Mode\". Increase for gentler starts; decrease for faster but more abrupt takeoffs."), ""}, + {"TrafficJerkDeceleration", tr("Braking Smoothness"), tr("How smoothly openpilot brakes in \"Traffic Mode\". Increase for gentler stops; decrease for quicker but sharper braking."), ""}, + {"TrafficJerkDanger", tr("Safety Gap Bias"), tr("How much extra space openpilot keeps from the vehicle ahead in \"Traffic Mode\". Increase for larger gaps and more cautious following; decrease for tighter gaps and closer following."), ""}, + {"TrafficJerkSpeedDecrease", tr("Slowdown Response"), tr("How smoothly openpilot slows down in \"Traffic Mode\". Increase for more gradual deceleration; decrease for faster but sharper slowdowns."), ""}, + {"TrafficJerkSpeed", tr("Speed-Up Response"), tr("How smoothly openpilot speeds up in \"Traffic Mode\". Increase for more gradual acceleration; decrease for quicker but more jolting acceleration."), ""}, + {"ResetTrafficPersonality", tr("Reset to Defaults"), tr("Reset \"Traffic Mode\" settings to defaults."), ""}, + + {"AggressivePersonalityProfile", tr("Aggressive"), tr("Customize the \"Aggressive\" personality profile. Designed for assertive driving with tighter gaps."), "../../frogpilot/assets/stock_theme/distance_icons/aggressive.png"}, + {"AggressiveFollow", tr("Following Distance"), tr("How many seconds openpilot follows behind lead vehicles when using the \"Aggressive\" profile. Increase for more space; decrease for tighter gaps.

Default: 1.25 seconds."), ""}, + {"AggressiveJerkAcceleration", tr("Acceleration Smoothness"), tr("How smoothly openpilot accelerates with the \"Aggressive\" profile. Increase for gentler starts; decrease for faster but more abrupt takeoffs."), ""}, + {"AggressiveJerkDeceleration", tr("Braking Smoothness"), tr("How smoothly openpilot brakes with the \"Aggressive\" profile. Increase for gentler stops; decrease for quicker but sharper braking."), ""}, + {"AggressiveJerkDanger", tr("Safety Gap Bias"), tr("How much extra space openpilot keeps from the vehicle ahead with the \"Aggressive\" profile. Increase for larger gaps and more cautious following; decrease for tighter gaps and closer following."), ""}, + {"AggressiveJerkSpeedDecrease", tr("Slowdown Response"), tr("How smoothly openpilot slows down with the \"Aggressive\" profile. Increase for more gradual deceleration; decrease for faster but sharper slowdowns."), ""}, + {"AggressiveJerkSpeed", tr("Speed-Up Response"), tr("How smoothly openpilot speeds up with the \"Aggressive\" profile. Increase for more gradual acceleration; decrease for quicker but more jolting acceleration."), ""}, + {"ResetAggressivePersonality", tr("Reset to Defaults"), tr("Reset the \"Aggressive\" profile to defaults."), ""}, + + {"StandardPersonalityProfile", tr("Standard"), tr("Customize the \"Standard\" personality profile. Designed for balanced driving with moderate gaps."), "../../frogpilot/assets/stock_theme/distance_icons/standard.png"}, + {"StandardFollow", tr("Following Distance"), tr("How many seconds openpilot follows behind lead vehicles when using the \"Standard\" profile. Increase for more space; decrease for tighter gaps.

Default: 1.45 seconds."), ""}, + {"StandardJerkAcceleration", tr("Acceleration Smoothness"), tr("How smoothly openpilot accelerates with the \"Standard\" profile. Increase for gentler starts; decrease for faster but more abrupt takeoffs."), ""}, + {"StandardJerkDeceleration", tr("Braking Smoothness"), tr("How smoothly openpilot brakes with the \"Standard\" profile. Increase for gentler stops; decrease for quicker but sharper braking."), ""}, + {"StandardJerkDanger", tr("Safety Gap Bias"), tr("How much extra space openpilot keeps from the vehicle ahead with the \"Standard\" profile. Increase for larger gaps and more cautious following; decrease for tighter gaps and closer following."), ""}, + {"StandardJerkSpeedDecrease", tr("Slowdown Response"), tr("How smoothly openpilot slows down with the \"Standard\" profile. Increase for more gradual deceleration; decrease for faster but sharper slowdowns."), ""}, + {"StandardJerkSpeed", tr("Speed-Up Response"), tr("How smoothly openpilot speeds up with the \"Standard\" profile. Increase for more gradual acceleration; decrease for quicker but more jolting acceleration."), ""}, + {"ResetStandardPersonality", tr("Reset to Defaults"), tr("Reset the \"Standard\" profile to defaults."), ""}, + + {"RelaxedPersonalityProfile", tr("Relaxed"), tr("Customize the \"Relaxed\" personality profile. Designed for smoother, more comfortable driving with larger gaps."), "../../frogpilot/assets/stock_theme/distance_icons/relaxed.png"}, + {"RelaxedFollow", tr("Following Distance"), tr("How many seconds openpilot follows behind lead vehicles when using the \"Relaxed\" profile. Increase for more space; decrease for tighter gaps.

Default: 1.75 seconds."), ""}, + {"RelaxedJerkAcceleration", tr("Acceleration Smoothness"), tr("How smoothly openpilot accelerates with the \"Relaxed\" profile. Increase for gentler starts; decrease for faster but more abrupt takeoffs."), ""}, + {"RelaxedJerkDeceleration", tr("Braking Smoothness"), tr("How smoothly openpilot brakes with the \"Relaxed\" profile. Increase for gentler stops; decrease for quicker but sharper braking."), ""}, + {"RelaxedJerkDanger", tr("Safety Gap Bias"), tr("How much extra space openpilot keeps from the vehicle ahead with the \"Relaxed\" profile. Increase for larger gaps and more cautious following; decrease for tighter gaps and closer following."), ""}, + {"RelaxedJerkSpeedDecrease", tr("Slowdown Response"), tr("How smoothly openpilot slows down with the \"Relaxed\" profile. Increase for more gradual deceleration; decrease for faster but sharper slowdowns."), ""}, + {"RelaxedJerkSpeed", tr("Speed-Up Response"), tr("How smoothly openpilot speeds up with the \"Relaxed\" profile. Increase for more gradual acceleration; decrease for quicker but more jolting acceleration."), ""}, + {"ResetRelaxedPersonality", tr("Reset to Defaults"), tr("Reset the \"Relaxed\" profile to defaults."), ""}, + + {"LongitudinalTune", tr("Longitudinal Tuning"), tr("Acceleration and braking control changes to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_longitudinal_tune.png"}, + {"AccelerationProfile", tr("Acceleration Profile"), tr("How quickly openpilot speeds up. \"Eco\" is gentle and efficient, \"Sport\" is firmer and more responsive, and \"Sport+\" accelerates at the maximum rate allowed."), ""}, + {"DecelerationProfile", tr("Deceleration Profile"), tr("How firmly openpilot slows down. \"Eco\" favors coasting, \"Sport\" applies stronger braking."), ""}, + {"HumanAcceleration", tr("Human-Like Acceleration"), tr("Acceleration that mimics human behavior by easing the throttle at low speeds and adding extra power when taking off from a stop."), ""}, + {"HumanFollowing", tr("Human-Like Following"), tr("Following behavior that mimics human drivers by closing gaps behind faster vehicles for quicker takeoffs and dynamically adjusting the desired following distance for gentler, more efficient braking."), ""}, + {"HumanLaneChanges", tr("Human-Like Lane Changes"), tr("Lane-change behavior that mimics human drivers by anticipating and tracking adjacent vehicles during lane changes."), ""}, + {"LeadDetectionThreshold", tr("Lead Detection Sensitivity"), tr("How sensitive openpilot is to detecting vehicles. Higher sensitivity allows quicker detection at longer distances but may react to non-vehicle objects; lower sensitivity is more conservative and reduces false detections."), ""}, + {"TacoTune", tr("\"Taco Bell Run\" Turn Speed Hack"), tr("The turn-speed hack from comma's 2022 \"Taco Bell Run\". Designed to slow down for left and right turns."), ""}, + + {"QOLLongitudinal", tr("Quality of Life"), tr("Miscellaneous acceleration and braking control changes to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_quality_of_life.png"}, + {"CustomCruise", tr("Cruise Interval"), tr("How much the set speed increases or decreases for each + or – cruise control button press."), ""}, + {"CustomCruiseLong", tr("Cruise Interval (Hold)"), tr("How much the set speed increases or decreases while holding the + or – cruise control buttons."), ""}, + {"ForceStops", tr("Force Stop at \"Detected\" Stop Lights/Signs"), tr("Force openpilot to stop whenever the driving model \"detects\" a red light or stop sign.

Disclaimer: openpilot does not explicitly detect traffic lights or stop signs. In \"Experimental Mode\", openpilot makes end-to-end driving decisions from camera input, which means it may stop even when there's no clear reason!"), ""}, + {"IncreasedStoppedDistance", tr("Increase Stopped Distance by:"), tr("Add extra space when stopped behind vehicles. Increase for more room; decrease for shorter gaps."), ""}, + {"MapGears", tr("Map Accel/Decel to Gears"), tr("Map the Acceleration or Deceleration profiles to the vehicle's \"Eco\" and \"Sport\" gear modes."), ""}, + {"SetSpeedOffset", tr("Offset Set Speed by:"), tr("Increase the set speed by the chosen offset. For example, set +5 if you usually drive 5 over the limit."), ""}, + {"ReverseCruise", tr("Reverse Cruise Increase"), tr("Reverse the cruise control button behavior so a short press increases the set speed by 5 instead of 1."), ""}, + {"WeatherPresets", tr("Weather Condition Offsets"), tr("Automatically adjust driving behavior based on real-time weather. Helps maintain comfort and safety in low visibility, rain, or snow."), ""}, + + {"LowVisibilityOffsets", tr("Low Visibility"), tr("Driving adjustments for fog, haze, or other low-visibility conditions."), ""}, + {"IncreaseFollowingLowVisibility", tr("Increase Following Distance by:"), tr("Add extra space behind lead vehicles in low visibility. Increase for more space; decrease for tighter gaps."), ""}, + {"IncreasedStoppedDistanceLowVisibility", tr("Increase Stopped Distance by:"), tr("Add extra buffer when stopped behind vehicles in low visibility. Increase for more room; decrease for shorter gaps."), ""}, + {"ReduceAccelerationLowVisibility", tr("Reduce Acceleration by:"), tr("Lower the maximum acceleration in low visibility. Increase for softer takeoffs; decrease for quicker but less stable takeoffs."), ""}, + {"ReduceLateralAccelerationLowVisibility", tr("Reduce Speed in Curves by:"), tr("Lower the desired speed while driving through curves in low visibility. Increase for safer, gentler turns; decrease for more aggressive driving in curves."), ""}, + + {"RainOffsets", tr("Rain"), tr("Driving adjustments for rainy conditions."), ""}, + {"IncreaseFollowingRain", tr("Increase Following Distance by:"), tr("Add extra space behind lead vehicles in rain. Increase for more space; decrease for tighter gaps."), ""}, + {"IncreasedStoppedDistanceRain", tr("Increase Stopped Distance by:"), tr("Add extra buffer when stopped behind vehicles in rain. Increase for more room; decrease for shorter gaps."), ""}, + {"ReduceAccelerationRain", tr("Reduce Acceleration by:"), tr("Lower the maximum acceleration in rain. Increase for softer takeoffs; decrease for quicker but less stable takeoffs."), ""}, + {"ReduceLateralAccelerationRain", tr("Reduce Speed in Curves by:"), tr("Lower the desired speed while driving through curves in rain. Increase for safer, gentler turns; decrease for more aggressive driving in curves."), ""}, + + {"RainStormOffsets", tr("Rainstorms"), tr("Driving adjustments for rainstorms."), ""}, + {"IncreaseFollowingRainStorm", tr("Increase Following Distance by:"), tr("Add extra space behind lead vehicles in a rainstorm. Increase for more space; decrease for tighter gaps."), ""}, + {"IncreasedStoppedDistanceRainStorm", tr("Increase Stopped Distance by:"), tr("Add extra buffer when stopped behind vehicles in a rainstorm. Increase for more room; decrease for shorter gaps."), ""}, + {"ReduceAccelerationRainStorm", tr("Reduce Acceleration by:"), tr("Lower the maximum acceleration in a rainstorm. Increase for softer takeoffs; decrease for quicker but less stable takeoffs."), ""}, + {"ReduceLateralAccelerationRainStorm", tr("Reduce Speed in Curves by:"), tr("Lower the desired speed while driving through curves in a rainstorm. Increase for safer, gentler turns; decrease for more aggressive driving in curves."), ""}, + + {"SnowOffsets", tr("Snow"), tr("Driving adjustments for snowy conditions."), ""}, + {"IncreaseFollowingSnow", tr("Increase Following Distance by:"), tr("Add extra space behind lead vehicles in snow. Increase for more space; decrease for tighter gaps."), ""}, + {"IncreasedStoppedDistanceSnow", tr("Increase Stopped Distance by:"), tr("Add extra buffer when stopped behind vehicles in snow. Increase for more room; decrease for shorter gaps."), ""}, + {"ReduceAccelerationSnow", tr("Reduce Acceleration by:"), tr("Lower the maximum acceleration in snow. Increase for softer takeoffs; decrease for quicker but less stable takeoffs."), ""}, + {"ReduceLateralAccelerationSnow", tr("Reduce Speed in Curves by:"), tr("Lower the desired speed while driving through curves in snow. Increase for safer, gentler turns; decrease for more aggressive driving in curves."), ""}, + + {"SetWeatherKey", tr("Set Your Own Key"), tr("Set your own \"OpenWeatherMap\" key to increase the weather update rate.

Personal keys grant 1,000 free calls per day, allowing for updates every minute. The default key is shared and only updates every 15 minutes."), ""}, + + {"SpeedLimitController", tr("Speed Limit Controller"), tr("Limit openpilot's maximum driving speed to the current speed limit obtained from downloaded maps, Mapbox, or the dashboard for supported vehicles (Ford, Genesis, Hyundai, Kia, Lexus, Toyota)."), "../../frogpilot/assets/toggle_icons/icon_speed_limit.png"}, + {"SLCFallback", tr("Fallback Speed"), tr("The speed used by \"Speed Limit Controller\" when no speed limit is found.

- Set Speed: Use the cruise set speed
- Experimental Mode: Estimate the limit using the driving model
- Previous Limit: Keep using the last confirmed limit"), ""}, + {"SLCOverride", tr("Override Speed"), tr("The speed used by \"Speed Limit Controller\" after you manually drive faster than the posted limit.

- Set with Gas Pedal: Use the highest speed reached while pressing the gas
- Max Set Speed: Use the cruise set speed

Overrides clear when openpilot disengages."), ""}, + {"SLCQOL", tr("Quality of Life"), tr("Miscellaneous \"Speed Limit Controller\" changes to fine-tune how openpilot drives."), ""}, + {"SLCConfirmation", tr("Confirm New Speed Limits"), tr("Ask before changing to a new speed limit. To accept, tap the flashing on-screen widget or press the Cruise Increase button. To deny, press the Cruise Decrease button or ignore the prompt for 30 seconds."), ""}, + {"SLCLookaheadHigher", tr("Higher Limit Lookahead Time"), tr("How far ahead openpilot anticipates upcoming higher speed limits from downloaded map data."), ""}, + {"SLCLookaheadLower", tr("Lower Limit Lookahead Time"), tr("How far ahead openpilot anticipates upcoming lower speed limits from downloaded map data."), ""}, + {"SetSpeedLimit", tr("Match Speed Limit on Engage"), tr("When openpilot is first enabled, automatically set the max speed to the current posted limit."), ""}, + {"SLCMapboxFiller", tr("Use Mapbox as Fallback"), tr("Use Mapbox speed-limit data when no other source is available."), ""}, + {"SLCPriority", tr("Speed Limit Source Priority"), tr("The source order for speed limits when more than one is available."), ""}, + {"SLCOffsets", tr("Speed Limit Offsets"), tr("Add an offset to the posted speed limit to better match your driving style."), ""}, + {"Offset1", tr("Speed Offset (0–24 mph)"), tr("How much to offset posted speed-limits between 0 and 24 mph."), ""}, + {"Offset2", tr("Speed Offset (25–34 mph)"), tr("How much to offset posted speed-limits between 25 and 34 mph."), ""}, + {"Offset3", tr("Speed Offset (35–44 mph)"), tr("How much to offset posted speed-limits between 35 and 44 mph."), ""}, + {"Offset4", tr("Speed Offset (45–54 mph)"), tr("How much to offset posted speed-limits between 45 and 54 mph."), ""}, + {"Offset5", tr("Speed Offset (55–64 mph)"), tr("How much to offset posted speed-limits between 55 and 64 mph."), ""}, + {"Offset6", tr("Speed Offset (65–74 mph)"), tr("How much to offset posted speed-limits between 65 and 74 mph."), ""}, + {"Offset7", tr("Speed Offset (75–99 mph)"), tr("How much to offset posted speed-limits between 75 and 99 mph."), ""}, + {"SLCVisuals", tr("Visual Settings"), tr("Visual \"Speed Limit Controller\" changes to fine-tune how the driving screen looks."), ""}, + {"ShowSLCOffset", tr("Show Speed Limit Offset"), tr("Show the current offset from the posted limit on the driving screen."), ""}, + {"SpeedLimitSources", tr("Show Speed Limit Sources"), tr("Display the speed-limit sources and their current values on the driving screen."), ""} + }; + + for (const auto &[param, title, desc, icon] : longitudinalToggles) { + AbstractControl *longitudinalToggle; + + if (param == "AdvancedLongitudinalTune") { + FrogPilotManageControl *advancedLongitudinalTuneToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(advancedLongitudinalTuneToggle, &FrogPilotManageControl::manageButtonClicked, [longitudinalLayout, advancedLongitudinalTunePanel]() { + longitudinalLayout->setCurrentWidget(advancedLongitudinalTunePanel); + }); + longitudinalToggle = advancedLongitudinalTuneToggle; + } else if (param == "LongitudinalActuatorDelay") { + longitudinalActuatorDelayToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 1, tr(" seconds"), std::map(), 0.01); + longitudinalToggle = longitudinalActuatorDelayToggle; + } else if (param == "MaxDesiredAcceleration") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0.1, 4.0, tr(" m/s²"), std::map(), 0.1); + } else if (param == "StartAccel") { + startAccelToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 4, tr(" m/s²"), std::map(), 0.01, true); + longitudinalToggle = startAccelToggle; + } else if (param == "VEgoStarting") { + vEgoStartingToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0.01, 1, tr(" m/s²"), std::map(), 0.01); + longitudinalToggle = vEgoStartingToggle; + } else if (param == "StopAccel") { + stopAccelToggle = new FrogPilotParamValueControl(param, title, desc, icon, -4, 0, tr(" m/s²"), std::map(), 0.01, true); + longitudinalToggle = stopAccelToggle; + } else if (param == "StoppingDecelRate") { + stoppingDecelRateToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0.001, 1, tr(" m/s²"), std::map(), 0.001, true); + longitudinalToggle = stoppingDecelRateToggle; + } else if (param == "VEgoStopping") { + vEgoStoppingToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0.01, 1, tr(" m/s²"), std::map(), 0.01); + longitudinalToggle = vEgoStoppingToggle; + + } else if (param == "ConditionalExperimental") { + FrogPilotManageControl *conditionalExperimentalToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(conditionalExperimentalToggle, &FrogPilotManageControl::manageButtonClicked, [longitudinalLayout, conditionalExperimentalPanel]() { + longitudinalLayout->setCurrentWidget(conditionalExperimentalPanel); + }); + longitudinalToggle = conditionalExperimentalToggle; + } else if (param == "CESpeed") { + FrogPilotParamValueControl *CESpeed = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, tr(" mph"), std::map(), 1, true, 175); + FrogPilotParamValueControl *CESpeedLead = new FrogPilotParamValueControl("CESpeedLead", tr("With Lead"), tr("Switch to \"Experimental Mode\" when driving below this speed with a lead to help openpilot handle low-speed situations more smoothly."), icon, 0, 99, tr(" mph"), std::map(), 1, true, 175); + FrogPilotDualParamValueControl *conditionalSpeeds = new FrogPilotDualParamValueControl(CESpeed, CESpeedLead); + longitudinalToggle = reinterpret_cast(conditionalSpeeds); + } else if (param == "CECurves") { + std::vector curveToggles{"CECurvesLead"}; + std::vector curveToggleNames{tr("With Lead")}; + longitudinalToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, curveToggles, curveToggleNames); + } else if (param == "CELead") { + std::vector leadToggles{"CESlowerLead", "CEStoppedLead"}; + std::vector leadToggleNames{tr("Slower Lead"), tr("Stopped Lead")}; + longitudinalToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, leadToggles, leadToggleNames); + } else if (param == "CEModelStopTime") { + std::map stopTimeLabels; + for (int i = 0; i <= 10; ++i) { + stopTimeLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" second") : QString::number(i) + tr(" seconds"); + } + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 9, QString(), stopTimeLabels); + } else if (param == "CESignalSpeed") { + std::vector ceSignalToggles{"CESignalLaneDetection"}; + std::vector ceSignalToggleNames{tr("Not For Detected Lanes")}; + longitudinalToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0, 99, tr(" mph"), std::map(), 1.0, true, ceSignalToggles, ceSignalToggleNames, true); + + } else if (param == "CurveSpeedController") { + FrogPilotManageControl *curveControlToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(curveControlToggle, &FrogPilotManageControl::manageButtonClicked, [longitudinalLayout, curveSpeedPanel]() { + longitudinalLayout->setCurrentWidget(curveSpeedPanel); + }); + longitudinalToggle = curveControlToggle; + } else if (param == "CalibrationProgress") { + calibrationProgressLabel = new LabelControl(title, QString::number(params.getFloat("CalibrationProgress"), 'f', 2) + "%", desc); + longitudinalToggle = calibrationProgressLabel; + } else if (param == "CalibratedLateralAcceleration") { + calibratedLateralAccelerationLabel = new LabelControl(title, QString::number(params.getFloat("CalibratedLateralAcceleration"), 'f', 2) + tr(" m/s²"), desc); + longitudinalToggle = calibratedLateralAccelerationLabel; + } else if (param == "ResetCurveData") { + ButtonControl *resetCurveDataButton = new ButtonControl(title, tr("RESET"), desc); + QObject::connect(resetCurveDataButton, &ButtonControl::clicked, [this]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely reset your curvature data?"), this)) { + params.putFloat("CalibratedLateralAcceleration", 2.00); + params.remove("CalibrationProgress"); + params.remove("CurvatureData"); + + calibratedLateralAccelerationLabel->setText(QString::number(2.00, 'f', 2) + tr(" m/s²")); + calibrationProgressLabel->setText(QString::number(0.00, 'f', 2) + "%"); + } + }); + longitudinalToggle = resetCurveDataButton; + + } else if (param == "CustomPersonalities") { + FrogPilotManageControl *customPersonalitiesToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(customPersonalitiesToggle, &FrogPilotManageControl::manageButtonClicked, [longitudinalLayout, customDrivingPersonalityPanel]() { + longitudinalLayout->setCurrentWidget(customDrivingPersonalityPanel); + }); + longitudinalToggle = customPersonalitiesToggle; + } else if (param == "ResetTrafficPersonality" || param == "ResetAggressivePersonality" || param == "ResetStandardPersonality" || param == "ResetRelaxedPersonality") { + ButtonControl *resetButton = new ButtonControl(title, tr("RESET"), desc); + longitudinalToggle = resetButton; + } else if (param == "TrafficPersonalityProfile") { + FrogPilotButtonsControl *trafficPersonalityToggle = new FrogPilotButtonsControl(title, desc, icon, {tr("MANAGE")}); + QObject::connect(trafficPersonalityToggle, &FrogPilotButtonsControl::buttonClicked, [longitudinalLayout, trafficPersonalityPanel, this](int id) { + openSubSubPanel(); + + longitudinalLayout->setCurrentWidget(trafficPersonalityPanel); + + customPersonalityOpen = true; + }); + longitudinalToggle = trafficPersonalityToggle; + } else if (param == "AggressivePersonalityProfile") { + FrogPilotButtonsControl *aggressivePersonalityToggle = new FrogPilotButtonsControl(title, desc, icon, {tr("MANAGE")}); + QObject::connect(aggressivePersonalityToggle, &FrogPilotButtonsControl::buttonClicked, [longitudinalLayout, aggressivePersonalityPanel, this](int id) { + openSubSubPanel(); + + longitudinalLayout->setCurrentWidget(aggressivePersonalityPanel); + + customPersonalityOpen = true; + }); + longitudinalToggle = aggressivePersonalityToggle; + } else if (param == "StandardPersonalityProfile") { + FrogPilotButtonsControl *standardPersonalityToggle = new FrogPilotButtonsControl(title, desc, icon, {tr("MANAGE")}); + QObject::connect(standardPersonalityToggle, &FrogPilotButtonsControl::buttonClicked, [longitudinalLayout, standardPersonalityPanel, this](int id) { + openSubSubPanel(); + + longitudinalLayout->setCurrentWidget(standardPersonalityPanel); + + customPersonalityOpen = true; + }); + longitudinalToggle = standardPersonalityToggle; + } else if (param == "RelaxedPersonalityProfile") { + FrogPilotButtonsControl *relaxedPersonalityToggle = new FrogPilotButtonsControl(title, desc, icon, {tr("MANAGE")}); + QObject::connect(relaxedPersonalityToggle, &FrogPilotButtonsControl::buttonClicked, [longitudinalLayout, relaxedPersonalityPanel, this](int id) { + openSubSubPanel(); + + longitudinalLayout->setCurrentWidget(relaxedPersonalityPanel); + + customPersonalityOpen = true; + }); + longitudinalToggle = relaxedPersonalityToggle; + } else if (aggressivePersonalityKeys.contains(param) || standardPersonalityKeys.contains(param) || relaxedPersonalityKeys.contains(param) || trafficPersonalityKeys.contains(param)) { + if (param == "TrafficFollow" || param == "AggressiveFollow" || param == "StandardFollow" || param == "RelaxedFollow") { + std::map followTimeLabels; + for (float i = 0; i <= 3; i += 0.01) { + followTimeLabels[i] = std::lround(i / 0.01) == 1 / 0.01 ? QString::number(i, 'f', 2) + tr(" second") : QString::number(i, 'f', 2) + tr(" seconds"); + } + if (param == "TrafficFollow") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0.5, 3, QString(), followTimeLabels, 0.01, true); + } else { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 1, 3, QString(), followTimeLabels, 0.01, true); + } + } else { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 25, 200, "%"); + } + + } else if (param == "LongitudinalTune") { + FrogPilotManageControl *longitudinalTuneToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(longitudinalTuneToggle, &FrogPilotManageControl::manageButtonClicked, [longitudinalLayout, longitudinalTunePanel]() { + longitudinalLayout->setCurrentWidget(longitudinalTunePanel); + }); + longitudinalToggle = longitudinalTuneToggle; + } else if (param == "AccelerationProfile") { + std::vector accelerationProfiles{tr("Standard"), tr("Eco"), tr("Sport"), tr("Sport+")}; + ButtonParamControl *accelerationProfileToggle = new ButtonParamControl(param, title, desc, icon, accelerationProfiles); + longitudinalToggle = accelerationProfileToggle; + } else if (param == "DecelerationProfile") { + std::vector decelerationProfiles{tr("Standard"), tr("Eco"), tr("Sport")}; + ButtonParamControl *decelerationProfileToggle = new ButtonParamControl(param, title, desc, icon, decelerationProfiles); + longitudinalToggle = decelerationProfileToggle; + } else if (param == "LeadDetectionThreshold") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 25, 50, "%"); + + } else if (param == "QOLLongitudinal") { + FrogPilotManageControl *qolLongitudinalToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(qolLongitudinalToggle, &FrogPilotManageControl::manageButtonClicked, [longitudinalLayout, qolPanel]() { + longitudinalLayout->setCurrentWidget(qolPanel); + }); + longitudinalToggle = qolLongitudinalToggle; + } else if (param == "CustomCruise") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 1, 99, tr(" mph")); + } else if (param == "CustomCruiseLong") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 1, 99, tr(" mph")); + } else if (param == "IncreasedStoppedDistance") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, tr(" feet")); + } else if (param == "MapGears") { + std::vector mapGearsToggles{"MapAcceleration", "MapDeceleration"}; + std::vector mapGearsToggleNames{tr("Acceleration"), tr("Deceleration")}; + longitudinalToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, mapGearsToggles, mapGearsToggleNames); + } else if (param == "SetSpeedOffset") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, tr(" mph")); + } else if (param == "WeatherPresets") { + FrogPilotManageControl *weatherToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(weatherToggle, &FrogPilotManageControl::manageButtonClicked, [longitudinalLayout, weatherPanel, this]() { + openSubSubPanel(); + + longitudinalLayout->setCurrentWidget(weatherPanel); + + qolOpen = true; + }); + longitudinalToggle = weatherToggle; + } else if (param == "SetWeatherKey") { + weatherKeyControl = new FrogPilotButtonsControl(title, desc, icon, {tr("ADD"), tr("TEST")}); + QObject::connect(weatherKeyControl, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + if (id == 0) { + if (!params.get("WeatherToken").empty()) { + if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to remove your key?"), this)) { + params.remove("WeatherToken"); + + weatherKeyControl->setText(0, tr("ADD")); + weatherKeyControl->setVisibleButton(1, false); + } + } else { + int keyLength = 32; + QString currentKey = QString::fromStdString(params.get("WeatherToken")); + QString newKey = InputDialog::getText(tr("Enter your \"OpenWeatherMap\" key"), this, tr("Characters: 0/%1").arg(keyLength), false, -1, currentKey, keyLength).trimmed(); + if (!newKey.isEmpty()) { + params.put("WeatherToken", newKey.toStdString()); + + weatherKeyControl->setText(0, tr("REMOVE")); + weatherKeyControl->setVisibleButton(1, true); + } + } + } else if (id == 1) { + weatherKeyControl->setValue(tr("Testing...")); + + QString key = QString::fromStdString(params.get("WeatherToken")).trimmed(); + QString url30 = QString("https://api.openweathermap.org/data/3.0/onecall?lat=42.4293&lon=-83.9850&exclude=current,minutely,hourly,daily,alerts&appid=%1").arg(key); + + QNetworkRequest request(url30); + QNetworkReply *reply = networkManager->get(request); + QObject::connect(reply, &QNetworkReply::finished, this, [=]() { + reply->deleteLater(); + + if (reply->error() == QNetworkReply::NoError) { + weatherKeyControl->setValue(""); + ConfirmationDialog::alert(tr("Key is valid!"), this); + return; + } + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status == 401 || status == 403) { + QString url25 = QString("https://api.openweathermap.org/data/2.5/weather?lat=42.4293&lon=-83.9850&appid=%1").arg(key); + + QNetworkRequest request25(url25); + QNetworkReply *reply25 = networkManager->get(request25); + QObject::connect(reply25, &QNetworkReply::finished, this, [=]() { + reply25->deleteLater(); + + weatherKeyControl->setValue(""); + if (reply25->error() == QNetworkReply::NoError) { + ConfirmationDialog::alert(tr("Your key is valid for version 2.5, but version 3.0 is highly recommended! Please subscribe to the \"One Call API 3.0\" plan!"), this); + } else { + int status25 = reply25->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + ConfirmationDialog::alert(tr("Invalid key! (Error: %1)").arg(status25), this); + } + }); + } else { + weatherKeyControl->setValue(""); + ConfirmationDialog::alert(tr("An error occurred: %1").arg(reply->errorString()), this); + } + }); + } + }); + longitudinalToggle = weatherKeyControl; + } else if (param == "LowVisibilityOffsets") { + ButtonControl *manageLowVisibilitOffsetsButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(manageLowVisibilitOffsetsButton, &ButtonControl::clicked, [longitudinalLayout, weatherLowVisibilityPanel, this]() { + openSubSubSubPanel(); + + longitudinalLayout->setCurrentWidget(weatherLowVisibilityPanel); + + weatherOpen = true; + }); + longitudinalToggle = manageLowVisibilitOffsetsButton; + } else if (param == "RainOffsets") { + ButtonControl *manageRainOffsetsButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(manageRainOffsetsButton, &ButtonControl::clicked, [longitudinalLayout, weatherRainPanel, this]() { + openSubSubSubPanel(); + + longitudinalLayout->setCurrentWidget(weatherRainPanel); + + weatherOpen = true; + }); + longitudinalToggle = manageRainOffsetsButton; + } else if (param == "RainStormOffsets") { + ButtonControl *manageRainStormOffsetsButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(manageRainStormOffsetsButton, &ButtonControl::clicked, [longitudinalLayout, weatherRainStormPanel, this]() { + openSubSubSubPanel(); + + longitudinalLayout->setCurrentWidget(weatherRainStormPanel); + + weatherOpen = true; + }); + longitudinalToggle = manageRainStormOffsetsButton; + } else if (param == "SnowOffsets") { + ButtonControl *manageSnowOffsetsButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(manageSnowOffsetsButton, &ButtonControl::clicked, [longitudinalLayout, weatherSnowPanel, this]() { + openSubSubSubPanel(); + + longitudinalLayout->setCurrentWidget(weatherSnowPanel); + + weatherOpen = true; + }); + longitudinalToggle = manageSnowOffsetsButton; + } else if (param == "IncreaseFollowingLowVisibility" || param == "IncreaseFollowingRain" || param == "IncreaseFollowingRainStorm" || param == "IncreaseFollowingSnow") { + std::map followTimeLabels; + for (float i = 0; i <= 3; i += 0.01) { + followTimeLabels[i] = std::lround(i / 0.01) == 1 / 0.01 ? QString::number(i, 'f', 2) + tr(" second") : QString::number(i, 'f', 2) + tr(" seconds"); + } + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 3, QString(), followTimeLabels, 0.01, true); + } else if (param == "IncreasedStoppedDistanceLowVisibility" || param == "IncreasedStoppedDistanceRain" || param == "IncreasedStoppedDistanceRainStorm" || param == "IncreasedStoppedDistanceSnow") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, tr(" feet")); + } else if (param == "ReduceAccelerationLowVisibility" || param == "ReduceAccelerationRain" || param == "ReduceAccelerationRainStorm" || param == "ReduceAccelerationSnow") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, "%", std::map(), 1); + } else if (param == "ReduceLateralAccelerationLowVisibility" || param == "ReduceLateralAccelerationRain" || param == "ReduceLateralAccelerationRainStorm" || param == "ReduceLateralAccelerationSnow") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, "%", std::map(), 1); + + } else if (param == "SpeedLimitController") { + FrogPilotManageControl *speedLimitControllerToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(speedLimitControllerToggle, &FrogPilotManageControl::manageButtonClicked, [longitudinalLayout, speedLimitControllerPanel]() { + longitudinalLayout->setCurrentWidget(speedLimitControllerPanel); + }); + longitudinalToggle = speedLimitControllerToggle; + } else if (param == "SLCFallback") { + std::vector fallbackOptions{tr("Set Speed"), tr("Experimental Mode"), tr("Previous Limit")}; + ButtonParamControl *fallbackSelection = new ButtonParamControl(param, title, desc, icon, fallbackOptions); + longitudinalToggle = fallbackSelection; + } else if (param == "SLCOverride") { + std::vector overrideOptions{tr("None"), tr("Set With Gas Pedal"), tr("Max Set Speed")}; + ButtonParamControl *overrideSelection = new ButtonParamControl(param, title, desc, icon, overrideOptions); + longitudinalToggle = overrideSelection; + } else if (param == "SLCPriority") { + ButtonControl *slcPriorityButton = new ButtonControl(title, tr("SELECT"), desc); + QStringList primaryPriorities = {tr("Dashboard"), tr("Map Data"), tr("Highest"), tr("Lowest")}; + QStringList otherPriorities = {tr("None"), tr("Dashboard"), tr("Map Data")}; + QStringList priorityPrompts = {tr("Select your primary priority"), tr("Select your secondary priority")}; + + QObject::connect(slcPriorityButton, &ButtonControl::clicked, [=]() { + QStringList selectedPriorities; + + for (int i = 1; i <= 2; ++i) { + QStringList availablePriorities = i == 1 ? primaryPriorities : otherPriorities; + availablePriorities = availablePriorities.toSet().subtract(selectedPriorities.toSet()).toList(); + + if (!parent->hasDashSpeedLimits) { + availablePriorities.removeAll(tr("Dashboard")); + } + if (availablePriorities.size() == 1 && availablePriorities.contains(tr("None"))) { + break; + } + + QString selection = MultiOptionDialog::getSelection(priorityPrompts[i - 1], availablePriorities, "", this); + if (selection.isEmpty()) { + break; + } + + selectedPriorities.append(selection); + + params.put(QString("SLCPriority%1").arg(i).toStdString(), selection.toStdString()); + if (selection == tr("None")) { + for (int j = i + 1; j <= 3; ++j) { + params.put(QString("SLCPriority%1").arg(j).toStdString(), tr("None").toStdString()); + } + break; + } + + if (selection == tr("Lowest") || selection == tr("Highest")) { + break; + } + } + + selectedPriorities.removeAll(tr("None")); + if (!selectedPriorities.isEmpty()) { + slcPriorityButton->setValue(selectedPriorities.join(", ")); + } + }); + + QStringList selectedPriorities; + for (int i = 1; i <= 2; ++i) { + QString priority = QString::fromStdString(params.get(QString("SLCPriority%1").arg(i).toStdString())); + if (primaryPriorities.contains(priority)) { + selectedPriorities.append(priority); + } + } + slcPriorityButton->setValue(selectedPriorities.join(", ")); + + longitudinalToggle = slcPriorityButton; + } else if (param == "SLCOffsets") { + ButtonControl *manageSLCOffsetsButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(manageSLCOffsetsButton, &ButtonControl::clicked, [longitudinalLayout, speedLimitControllerOffsetsPanel, this]() { + openSubSubPanel(); + + longitudinalLayout->setCurrentWidget(speedLimitControllerOffsetsPanel); + + slcOpen = true; + }); + longitudinalToggle = manageSLCOffsetsButton; + } else if (speedLimitControllerOffsetsKeys.contains(param)) { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, -99, 99, tr(" mph")); + } else if (param == "SLCQOL") { + ButtonControl *manageSLCQOLButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(manageSLCQOLButton, &ButtonControl::clicked, [longitudinalLayout, speedLimitControllerQOLPanel, this]() { + openSubSubPanel(); + + longitudinalLayout->setCurrentWidget(speedLimitControllerQOLPanel); + + slcOpen = true; + }); + longitudinalToggle = manageSLCQOLButton; + } else if (param == "SLCConfirmation") { + std::vector confirmationToggles{"SLCConfirmationLower", "SLCConfirmationHigher"}; + std::vector confirmationToggleNames{tr("Lower Limits"), tr("Higher Limits")}; + longitudinalToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, confirmationToggles, confirmationToggleNames); + } else if (param == "SLCLookaheadHigher" || param == "SLCLookaheadLower") { + longitudinalToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 30, tr(" seconds")); + } else if (param == "SLCVisuals") { + ButtonControl *manageSLCVisualsButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(manageSLCVisualsButton, &ButtonControl::clicked, [longitudinalLayout, speedLimitControllerVisualPanel, this]() { + openSubSubPanel(); + + longitudinalLayout->setCurrentWidget(speedLimitControllerVisualPanel); + + slcOpen = true; + }); + longitudinalToggle = manageSLCVisualsButton; + + } else { + longitudinalToggle = new ParamControl(param, title, desc, icon); + } + + toggles[param] = longitudinalToggle; + + if (advancedLongitudinalTuneKeys.contains(param)) { + advancedLongitudinalTuneList->addItem(longitudinalToggle); + } else if (aggressivePersonalityKeys.contains(param)) { + aggressivePersonalityList->addItem(longitudinalToggle); + } else if (conditionalExperimentalKeys.contains(param)) { + conditionalExperimentalList->addItem(longitudinalToggle); + } else if (curveSpeedKeys.contains(param)) { + curveSpeedList->addItem(longitudinalToggle); + } else if (customDrivingPersonalityKeys.contains(param)) { + customDrivingPersonalityList->addItem(longitudinalToggle); + } else if (longitudinalTuneKeys.contains(param)) { + longitudinalTuneList->addItem(longitudinalToggle); + } else if (qolKeys.contains(param)) { + qolList->addItem(longitudinalToggle); + } else if (relaxedPersonalityKeys.contains(param)) { + relaxedPersonalityList->addItem(longitudinalToggle); + } else if (speedLimitControllerKeys.contains(param)) { + speedLimitControllerList->addItem(longitudinalToggle); + } else if (speedLimitControllerOffsetsKeys.contains(param)) { + speedLimitControllerOffsetsList->addItem(longitudinalToggle); + } else if (speedLimitControllerQOLKeys.contains(param)) { + speedLimitControllerQOLList->addItem(longitudinalToggle); + } else if (speedLimitControllerVisualKeys.contains(param)) { + speedLimitControllerVisualList->addItem(longitudinalToggle); + } else if (standardPersonalityKeys.contains(param)) { + standardPersonalityList->addItem(longitudinalToggle); + } else if (trafficPersonalityKeys.contains(param)) { + trafficPersonalityList->addItem(longitudinalToggle); + } else if (weatherKeys.contains(param)) { + weatherList->addItem(longitudinalToggle); + } else if (weatherLowVisibilityKeys.contains(param)) { + weatherLowVisibilityList->addItem(longitudinalToggle); + } else if (weatherRainKeys.contains(param)) { + weatherRainList->addItem(longitudinalToggle); + } else if (weatherRainStormKeys.contains(param)) { + weatherRainStormList->addItem(longitudinalToggle); + } else if (weatherSnowKeys.contains(param)) { + weatherSnowList->addItem(longitudinalToggle); + } else { + longitudinalList->addItem(longitudinalToggle); + + parentKeys.insert(param); + } + + if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast(longitudinalToggle)) { + QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { + emit openSubPanel(); + openDescriptions(forceOpenDescriptions, toggles); + }); + } + + QObject::connect(longitudinalToggle, &AbstractControl::hideDescriptionEvent, [this]() { + update(); + }); + QObject::connect(longitudinalToggle, &AbstractControl::showDescriptionEvent, [this]() { + update(); + }); + } + + QSet forceUpdateKeys = {"HumanAcceleration", "LongitudinalTune"}; + for (const QString &key : forceUpdateKeys) { + QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, this, &FrogPilotLongitudinalPanel::updateToggles); + } + + FrogPilotParamValueControl *trafficFollowToggle = static_cast(toggles["TrafficFollow"]); + FrogPilotParamValueControl *trafficAccelerationToggle = static_cast(toggles["TrafficJerkAcceleration"]); + FrogPilotParamValueControl *trafficDecelerationToggle = static_cast(toggles["TrafficJerkDeceleration"]); + FrogPilotParamValueControl *trafficDangerToggle = static_cast(toggles["TrafficJerkDanger"]); + FrogPilotParamValueControl *trafficSpeedToggle = static_cast(toggles["TrafficJerkSpeed"]); + FrogPilotParamValueControl *trafficSpeedDecreaseToggle = static_cast(toggles["TrafficJerkSpeedDecrease"]); + FrogPilotButtonsControl *trafficResetButton = static_cast(toggles["ResetTrafficPersonality"]); + QObject::connect(trafficResetButton, &FrogPilotButtonsControl::buttonClicked, [=]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely reset your settings for Traffic Mode?"), this)) { + params.putFloat("TrafficFollow", std::stof(params.getKeyDefaultValue("TrafficFollow").value())); + params.putFloat("TrafficJerkAcceleration", std::stof(params.getKeyDefaultValue("TrafficJerkAcceleration").value())); + params.putFloat("TrafficJerkDeceleration", std::stof(params.getKeyDefaultValue("TrafficJerkDeceleration").value())); + params.putFloat("TrafficJerkDanger", std::stof(params.getKeyDefaultValue("TrafficJerkDanger").value())); + params.putFloat("TrafficJerkSpeed", std::stof(params.getKeyDefaultValue("TrafficJerkSpeed").value())); + params.putFloat("TrafficJerkSpeedDecrease", std::stof(params.getKeyDefaultValue("TrafficJerkSpeedDecrease").value())); + + trafficFollowToggle->refresh(); + trafficAccelerationToggle->refresh(); + trafficDecelerationToggle->refresh(); + trafficDangerToggle->refresh(); + trafficSpeedToggle->refresh(); + trafficSpeedDecreaseToggle->refresh(); + } + }); + + FrogPilotParamValueControl *aggressiveFollowToggle = static_cast(toggles["AggressiveFollow"]); + FrogPilotParamValueControl *aggressiveAccelerationToggle = static_cast(toggles["AggressiveJerkAcceleration"]); + FrogPilotParamValueControl *aggressiveDecelerationToggle = static_cast(toggles["AggressiveJerkDeceleration"]); + FrogPilotParamValueControl *aggressiveDangerToggle = static_cast(toggles["AggressiveJerkDanger"]); + FrogPilotParamValueControl *aggressiveSpeedToggle = static_cast(toggles["AggressiveJerkSpeed"]); + FrogPilotParamValueControl *aggressiveSpeedDecreaseToggle = static_cast(toggles["AggressiveJerkSpeedDecrease"]); + FrogPilotButtonsControl *aggressiveResetButton = static_cast(toggles["ResetAggressivePersonality"]); + QObject::connect(aggressiveResetButton, &FrogPilotButtonsControl::buttonClicked, [=]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely reset your settings for the Aggressive personality?"), this)) { + params.putFloat("AggressiveFollow", std::stof(params.getKeyDefaultValue("AggressiveFollow").value())); + params.putFloat("AggressiveJerkAcceleration", std::stof(params.getKeyDefaultValue("AggressiveJerkAcceleration").value())); + params.putFloat("AggressiveJerkDeceleration", std::stof(params.getKeyDefaultValue("AggressiveJerkDeceleration").value())); + params.putFloat("AggressiveJerkDanger", std::stof(params.getKeyDefaultValue("AggressiveJerkDanger").value())); + params.putFloat("AggressiveJerkSpeed", std::stof(params.getKeyDefaultValue("AggressiveJerkSpeed").value())); + params.putFloat("AggressiveJerkSpeedDecrease", std::stof(params.getKeyDefaultValue("AggressiveJerkSpeedDecrease").value())); + + aggressiveFollowToggle->refresh(); + aggressiveAccelerationToggle->refresh(); + aggressiveDecelerationToggle->refresh(); + aggressiveDangerToggle->refresh(); + aggressiveSpeedToggle->refresh(); + aggressiveSpeedDecreaseToggle->refresh(); + } + }); + + FrogPilotParamValueControl *standardFollowToggle = static_cast(toggles["StandardFollow"]); + FrogPilotParamValueControl *standardAccelerationToggle = static_cast(toggles["StandardJerkAcceleration"]); + FrogPilotParamValueControl *standardDecelerationToggle = static_cast(toggles["StandardJerkDeceleration"]); + FrogPilotParamValueControl *standardDangerToggle = static_cast(toggles["StandardJerkDanger"]); + FrogPilotParamValueControl *standardSpeedToggle = static_cast(toggles["StandardJerkSpeed"]); + FrogPilotParamValueControl *standardSpeedDecreaseToggle = static_cast(toggles["StandardJerkSpeedDecrease"]); + FrogPilotButtonsControl *standardResetButton = static_cast(toggles["ResetStandardPersonality"]); + QObject::connect(standardResetButton, &FrogPilotButtonsControl::buttonClicked, [=]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely reset your settings for the Standard personality?"), this)) { + params.putFloat("StandardFollow", std::stof(params.getKeyDefaultValue("StandardFollow").value())); + params.putFloat("StandardJerkAcceleration", std::stof(params.getKeyDefaultValue("StandardJerkAcceleration").value())); + params.putFloat("StandardJerkDeceleration", std::stof(params.getKeyDefaultValue("StandardJerkDeceleration").value())); + params.putFloat("StandardJerkDanger", std::stof(params.getKeyDefaultValue("StandardJerkDanger").value())); + params.putFloat("StandardJerkSpeed", std::stof(params.getKeyDefaultValue("StandardJerkSpeed").value())); + params.putFloat("StandardJerkSpeedDecrease", std::stof(params.getKeyDefaultValue("StandardJerkSpeedDecrease").value())); + + standardFollowToggle->refresh(); + standardAccelerationToggle->refresh(); + standardDecelerationToggle->refresh(); + standardDangerToggle->refresh(); + standardSpeedToggle->refresh(); + standardSpeedDecreaseToggle->refresh(); + } + }); + + FrogPilotParamValueControl *relaxedFollowToggle = static_cast(toggles["RelaxedFollow"]); + FrogPilotParamValueControl *relaxedAccelerationToggle = static_cast(toggles["RelaxedJerkAcceleration"]); + FrogPilotParamValueControl *relaxedDecelerationToggle = static_cast(toggles["RelaxedJerkDeceleration"]); + FrogPilotParamValueControl *relaxedDangerToggle = static_cast(toggles["RelaxedJerkDanger"]); + FrogPilotParamValueControl *relaxedSpeedToggle = static_cast(toggles["RelaxedJerkSpeed"]); + FrogPilotParamValueControl *relaxedSpeedDecreaseToggle = static_cast(toggles["RelaxedJerkSpeedDecrease"]); + FrogPilotButtonsControl *relaxedResetButton = static_cast(toggles["ResetRelaxedPersonality"]); + QObject::connect(relaxedResetButton, &FrogPilotButtonsControl::buttonClicked, [=]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely reset your settings for the Relaxed personality?"), this)) { + params.putFloat("RelaxedFollow", std::stof(params.getKeyDefaultValue("RelaxedFollow").value())); + params.putFloat("RelaxedJerkAcceleration", std::stof(params.getKeyDefaultValue("RelaxedJerkAcceleration").value())); + params.putFloat("RelaxedJerkDeceleration", std::stof(params.getKeyDefaultValue("RelaxedJerkDeceleration").value())); + params.putFloat("RelaxedJerkDanger", std::stof(params.getKeyDefaultValue("RelaxedJerkDanger").value())); + params.putFloat("RelaxedJerkSpeed", std::stof(params.getKeyDefaultValue("RelaxedJerkSpeed").value())); + params.putFloat("RelaxedJerkSpeedDecrease", std::stof(params.getKeyDefaultValue("RelaxedJerkSpeedDecrease").value())); + + relaxedFollowToggle->refresh(); + relaxedAccelerationToggle->refresh(); + relaxedDecelerationToggle->refresh(); + relaxedDangerToggle->refresh(); + relaxedSpeedToggle->refresh(); + relaxedSpeedDecreaseToggle->refresh(); + } + }); + + openDescriptions(forceOpenDescriptions, toggles); + + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [longitudinalLayout, longitudinalPanel, this] { + openDescriptions(forceOpenDescriptions, toggles); + longitudinalLayout->setCurrentWidget(longitudinalPanel); + }); + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubSubPanel, [longitudinalLayout, customDrivingPersonalityPanel, qolPanel, speedLimitControllerPanel, this]() { + openDescriptions(forceOpenDescriptions, toggles); + + if (customPersonalityOpen) { + longitudinalLayout->setCurrentWidget(customDrivingPersonalityPanel); + + customPersonalityOpen = false; + } else if (qolOpen) { + longitudinalLayout->setCurrentWidget(qolPanel); + + qolOpen = false; + } else if (slcOpen) { + longitudinalLayout->setCurrentWidget(speedLimitControllerPanel); + + slcOpen = false; + } + }); + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubSubSubPanel, [longitudinalLayout, weatherPanel, this]() { + openDescriptions(forceOpenDescriptions, toggles); + + if (weatherOpen) { + longitudinalLayout->setCurrentWidget(weatherPanel); + + weatherOpen = false; + } + }); + QObject::connect(parent, &FrogPilotSettingsWindow::updateMetric, this, &FrogPilotLongitudinalPanel::updateMetric); +} + +void FrogPilotLongitudinalPanel::showEvent(QShowEvent *event) { + FrogPilotUIState &fs = *frogpilotUIState(); + + calibratedLateralAccelerationLabel->setText(QString::number(params.getFloat("CalibratedLateralAcceleration"), 'f', 2) + tr(" m/s²")); + calibrationProgressLabel->setText(QString::number(params.getFloat("CalibrationProgress"), 'f', 2) + "%"); + + longitudinalActuatorDelayToggle->setTitle(QString(tr("Actuator Delay (Default: %1)")).arg(QString::number(parent->longitudinalActuatorDelay, 'f', 2))); + startAccelToggle->setTitle(QString(tr("Start Acceleration (Default: %1)")).arg(QString::number(parent->startAccel, 'f', 2))); + stopAccelToggle->setTitle(QString(tr("Stop Acceleration (Default: %1)")).arg(QString::number(parent->stopAccel, 'f', 2))); + stoppingDecelRateToggle->setTitle(QString(tr("Stopping Rate (Default: %1)")).arg(QString::number(parent->stoppingDecelRate, 'f', 2))); + vEgoStartingToggle->setTitle(QString(tr("Start Speed (Default: %1)")).arg(QString::number(parent->vEgoStarting, 'f', 2))); + vEgoStoppingToggle->setTitle(QString(tr("Stop Speed (Default: %1)")).arg(QString::number(parent->vEgoStopping, 'f', 2))); + + bool keyExists = !params.get("WeatherToken").empty(); + weatherKeyControl->setText(0, keyExists ? tr("REMOVE") : tr("ADD")); + weatherKeyControl->setVisibleButton(1, keyExists && fs.frogpilot_scene.online); + + updateToggles(); +} + +void FrogPilotLongitudinalPanel::updateMetric(bool metric, bool bootRun) { + static bool previousMetric; + if (metric != previousMetric && !bootRun) { + double distanceConversion = metric ? FOOT_TO_METER : METER_TO_FOOT; + double speedConversion = metric ? MILE_TO_KM : KM_TO_MILE; + + params.putIntNonBlocking("IncreasedStoppedDistance", params.getInt("IncreasedStoppedDistance") * distanceConversion); + params.putIntNonBlocking("IncreasedStoppedDistanceLowVisibility", params.getInt("IncreasedStoppedDistanceLowVisibility") * distanceConversion); + params.putIntNonBlocking("IncreasedStoppedDistanceRain", params.getInt("IncreasedStoppedDistanceRain") * distanceConversion); + params.putIntNonBlocking("IncreasedStoppedDistanceRainStorm", params.getInt("IncreasedStoppedDistanceRainStorm") * distanceConversion); + params.putIntNonBlocking("IncreasedStoppedDistanceSnow", params.getInt("IncreasedStoppedDistanceSnow") * distanceConversion); + + params.putIntNonBlocking("CESignalSpeed", params.getInt("CESignalSpeed") * speedConversion); + params.putIntNonBlocking("CESpeed", params.getInt("CESpeed") * speedConversion); + params.putIntNonBlocking("CESpeedLead", params.getInt("CESpeedLead") * speedConversion); + params.putIntNonBlocking("CustomCruise", params.getInt("CustomCruise") * speedConversion); + params.putIntNonBlocking("CustomCruiseLong", params.getInt("CustomCruiseLong") * speedConversion); + params.putIntNonBlocking("Offset1", params.getInt("Offset1") * speedConversion); + params.putIntNonBlocking("Offset2", params.getInt("Offset2") * speedConversion); + params.putIntNonBlocking("Offset3", params.getInt("Offset3") * speedConversion); + params.putIntNonBlocking("Offset4", params.getInt("Offset4") * speedConversion); + params.putIntNonBlocking("Offset5", params.getInt("Offset5") * speedConversion); + params.putIntNonBlocking("Offset6", params.getInt("Offset6") * speedConversion); + params.putIntNonBlocking("Offset7", params.getInt("Offset7") * speedConversion); + params.putIntNonBlocking("SetSpeedOffset", params.getInt("SetSpeedOffset") * speedConversion); + } + previousMetric = metric; + + static std::map imperialDistanceLabels; + static std::map imperialSpeedLabels; + static std::map metricDistanceLabels; + static std::map metricSpeedLabels; + + static bool labelsInitialized = false; + if (!labelsInitialized) { + for (int i = 0; i <= 10; ++i) { + imperialDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" foot") : QString::number(i) + tr(" feet"); + } + + for (int i = 0; i <= 99; ++i) { + imperialSpeedLabels[i] = i == 0 ? tr("Off") : QString::number(i) + tr(" mph"); + } + + for (int i = 0; i <= 3; ++i) { + metricDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" meter") : QString::number(i) + tr(" meters"); + } + + for (int i = 0; i <= 150; ++i) { + metricSpeedLabels[i] = i == 0 ? tr("Off") : QString::number(i) + tr(" km/h"); + } + + labelsInitialized = true; + } + + FrogPilotDualParamValueControl *ceSpeedToggle = reinterpret_cast(toggles["CESpeed"]); + FrogPilotParamValueButtonControl *ceSignal = static_cast(toggles["CESignalSpeed"]); + FrogPilotParamValueControl *customCruiseToggle = static_cast(toggles["CustomCruise"]); + FrogPilotParamValueControl *customCruiseLongToggle = static_cast(toggles["CustomCruiseLong"]); + FrogPilotParamValueControl *offset1Toggle = static_cast(toggles["Offset1"]); + FrogPilotParamValueControl *offset2Toggle = static_cast(toggles["Offset2"]); + FrogPilotParamValueControl *offset3Toggle = static_cast(toggles["Offset3"]); + FrogPilotParamValueControl *offset4Toggle = static_cast(toggles["Offset4"]); + FrogPilotParamValueControl *offset5Toggle = static_cast(toggles["Offset5"]); + FrogPilotParamValueControl *offset6Toggle = static_cast(toggles["Offset6"]); + FrogPilotParamValueControl *offset7Toggle = static_cast(toggles["Offset7"]); + FrogPilotParamValueControl *increasedStoppedDistanceToggle = static_cast(toggles["IncreasedStoppedDistance"]); + FrogPilotParamValueControl *increasedStoppedDistanceLowVisibilityToggle = static_cast(toggles["IncreasedStoppedDistanceLowVisibility"]); + FrogPilotParamValueControl *increasedStoppedDistanceRainToggle = static_cast(toggles["IncreasedStoppedDistanceRain"]); + FrogPilotParamValueControl *increasedStoppedDistanceRainStormToggle = static_cast(toggles["IncreasedStoppedDistanceRainStorm"]); + FrogPilotParamValueControl *increasedStoppedDistanceSnowToggle = static_cast(toggles["IncreasedStoppedDistanceSnow"]); + FrogPilotParamValueControl *setSpeedOffsetToggle = static_cast(toggles["SetSpeedOffset"]); + + if (metric) { + offset1Toggle->setTitle(tr("Speed Offset (0–29 km/h)")); + offset2Toggle->setTitle(tr("Speed Offset (30–49 km/h)")); + offset3Toggle->setTitle(tr("Speed Offset (50–59 km/h)")); + offset4Toggle->setTitle(tr("Speed Offset (60–79 km/h)")); + offset5Toggle->setTitle(tr("Speed Offset (80–99 km/h)")); + offset6Toggle->setTitle(tr("Speed Offset (100–119 km/h)")); + offset7Toggle->setTitle(tr("Speed Offset (120–140 km/h)")); + + offset1Toggle->setDescription(tr("How much to offset posted speed-limits between 0 and 24 mph.")); + offset2Toggle->setDescription(tr("How much to offset posted speed-limits between 25 and 34 mph.")); + offset3Toggle->setDescription(tr("How much to offset posted speed-limits between 35 and 44 mph.")); + offset4Toggle->setDescription(tr("How much to offset posted speed-limits between 45 and 54 mph.")); + offset5Toggle->setDescription(tr("How much to offset posted speed-limits between 55 and 64 mph.")); + offset6Toggle->setDescription(tr("How much to offset posted speed-limits between 65 and 74 mph.")); + offset7Toggle->setDescription(tr("How much to offset posted speed-limits between 75 and 99 mph.")); + + increasedStoppedDistanceToggle->updateControl(0, 3, metricDistanceLabels); + increasedStoppedDistanceLowVisibilityToggle->updateControl(0, 3, metricDistanceLabels); + increasedStoppedDistanceRainToggle->updateControl(0, 3, metricDistanceLabels); + increasedStoppedDistanceRainStormToggle->updateControl(0, 3, metricDistanceLabels); + increasedStoppedDistanceSnowToggle->updateControl(0, 3, metricDistanceLabels); + + ceSignal->updateControl(0, 150, metricSpeedLabels); + ceSpeedToggle->updateControl(0, 150, metricSpeedLabels); + customCruiseToggle->updateControl(1, 150, metricSpeedLabels); + customCruiseLongToggle->updateControl(1, 150, metricSpeedLabels); + offset1Toggle->updateControl(-150, 150, metricSpeedLabels); + offset2Toggle->updateControl(-150, 150, metricSpeedLabels); + offset3Toggle->updateControl(-150, 150, metricSpeedLabels); + offset4Toggle->updateControl(-150, 150, metricSpeedLabels); + offset5Toggle->updateControl(-150, 150, metricSpeedLabels); + offset6Toggle->updateControl(-150, 150, metricSpeedLabels); + offset7Toggle->updateControl(-150, 150, metricSpeedLabels); + setSpeedOffsetToggle->updateControl(-150, 150, metricSpeedLabels); + } else { + offset1Toggle->setTitle(tr("Speed Offset (0–24 mph)")); + offset2Toggle->setTitle(tr("Speed Offset (25–34 mph)")); + offset3Toggle->setTitle(tr("Speed Offset (35–44 mph)")); + offset4Toggle->setTitle(tr("Speed Offset (45–54 mph)")); + offset5Toggle->setTitle(tr("Speed Offset (55–64 mph)")); + offset6Toggle->setTitle(tr("Speed Offset (65–74 mph)")); + offset7Toggle->setTitle(tr("Speed Offset (75–99 mph)")); + + offset1Toggle->setDescription(tr("How much to offset posted speed-limits between 0 and 24 mph.")); + offset2Toggle->setDescription(tr("How much to offset posted speed-limits between 25 and 34 mph.")); + offset3Toggle->setDescription(tr("How much to offset posted speed-limits between 35 and 44 mph.")); + offset4Toggle->setDescription(tr("How much to offset posted speed-limits between 45 and 54 mph.")); + offset5Toggle->setDescription(tr("How much to offset posted speed-limits between 55 and 64 mph.")); + offset6Toggle->setDescription(tr("How much to offset posted speed-limits between 65 and 74 mph.")); + offset7Toggle->setDescription(tr("How much to offset posted speed-limits between 75 and 99 mph.")); + + increasedStoppedDistanceToggle->updateControl(0, 10, imperialDistanceLabels); + increasedStoppedDistanceLowVisibilityToggle->updateControl(0, 10, imperialDistanceLabels); + increasedStoppedDistanceRainToggle->updateControl(0, 10, imperialDistanceLabels); + increasedStoppedDistanceRainStormToggle->updateControl(0, 10, imperialDistanceLabels); + increasedStoppedDistanceSnowToggle->updateControl(0, 10, imperialDistanceLabels); + + ceSignal->updateControl(0, 99, imperialSpeedLabels); + ceSpeedToggle->updateControl(0, 99, imperialSpeedLabels); + customCruiseToggle->updateControl(1, 99, imperialSpeedLabels); + customCruiseLongToggle->updateControl(1, 99, imperialSpeedLabels); + offset1Toggle->updateControl(-99, 99, imperialSpeedLabels); + offset2Toggle->updateControl(-99, 99, imperialSpeedLabels); + offset3Toggle->updateControl(-99, 99, imperialSpeedLabels); + offset4Toggle->updateControl(-99, 99, imperialSpeedLabels); + offset5Toggle->updateControl(-99, 99, imperialSpeedLabels); + offset6Toggle->updateControl(-99, 99, imperialSpeedLabels); + offset7Toggle->updateControl(-99, 99, imperialSpeedLabels); + setSpeedOffsetToggle->updateControl(0, 99, imperialSpeedLabels); + } +} + +void FrogPilotLongitudinalPanel::updateToggles() { + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + toggle->setVisible(false); + } + } + + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + continue; + } + + bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble(); + + if (key == "CEStopLights") { + setVisible &= parent->tuningLevel < parent->frogpilotToggleLevels["CEModelStopTime"].toDouble(); + } + + else if (key == "CustomCruise" || key == "CustomCruiseLong" || key == "SetSpeedLimit" || key == "SetSpeedOffset") { + setVisible &= !parent->hasPCMCruise; + } + + else if (key == "HumanLaneChanges") { + setVisible &= parent->hasRadar; + } + + else if (key == "MapGears") { + setVisible &= parent->isToyota; + setVisible &= !parent->isTSK; + } + + else if (key == "ReverseCruise") { + setVisible &= parent->isToyota; + } + + else if (key == "SLCMapboxFiller") { + setVisible &= !params.get("MapboxSecretKey").empty(); + } + + else if (key == "StartAccel") { + setVisible &= !(params.getBool("LongitudinalTune") && params.getBool("HumanAcceleration")); + } + + else if (key == "StoppingDecelRate" || key == "VEgoStarting" || key == "VEgoStopping") { + setVisible &= !parent->isGM || !params.getBool("ExperimentalGMTune"); + setVisible &= !parent->isToyota || !params.getBool("FrogsGoMoosTweak"); + } + + toggle->setVisible(setVisible); + + if (setVisible) { + if (advancedLongitudinalTuneKeys.contains(key)) { + toggles["AdvancedLongitudinalTune"]->setVisible(true); + } else if (aggressivePersonalityKeys.contains(key)) { + toggles["AggressivePersonalityProfile"]->setVisible(true); + } else if (conditionalExperimentalKeys.contains(key)) { + toggles["ConditionalExperimental"]->setVisible(true); + } else if (curveSpeedKeys.contains(key)) { + toggles["CurveSpeedController"]->setVisible(true); + } else if (customDrivingPersonalityKeys.contains(key)) { + toggles["CustomPersonalities"]->setVisible(true); + } else if (longitudinalTuneKeys.contains(key)) { + toggles["LongitudinalTune"]->setVisible(true); + } else if (qolKeys.contains(key)) { + toggles["QOLLongitudinal"]->setVisible(true); + } else if (relaxedPersonalityKeys.contains(key)) { + toggles["RelaxedPersonalityProfile"]->setVisible(true); + } else if (speedLimitControllerKeys.contains(key)) { + toggles["SpeedLimitController"]->setVisible(true); + } else if (speedLimitControllerOffsetsKeys.contains(key)) { + toggles["SLCOffsets"]->setVisible(true); + } else if (speedLimitControllerQOLKeys.contains(key)) { + toggles["SLCQOL"]->setVisible(true); + } else if (speedLimitControllerVisualKeys.contains(key)) { + toggles["SLCVisuals"]->setVisible(true); + } else if (standardPersonalityKeys.contains(key)) { + toggles["StandardPersonalityProfile"]->setVisible(true); + } else if (trafficPersonalityKeys.contains(key)) { + toggles["TrafficPersonalityProfile"]->setVisible(true); + } + } + } + + openDescriptions(forceOpenDescriptions, toggles); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/longitudinal_settings.h b/frogpilot/ui/qt/offroad/longitudinal_settings.h new file mode 100644 index 000000000..78bc9ceb5 --- /dev/null +++ b/frogpilot/ui/qt/offroad/longitudinal_settings.h @@ -0,0 +1,70 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotLongitudinalPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotLongitudinalPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +signals: + void openSubPanel(); + void openSubSubPanel(); + void openSubSubSubPanel(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void updateMetric(bool metric, bool bootRun); + void updateToggles(); + + bool customPersonalityOpen; + bool forceOpenDescriptions; + bool qolOpen; + bool slcOpen; + bool weatherOpen; + + std::map toggles; + + QSet advancedLongitudinalTuneKeys = {"LongitudinalActuatorDelay", "MaxDesiredAcceleration", "StartAccel", "StopAccel", "StoppingDecelRate", "VEgoStarting", "VEgoStopping"}; + QSet aggressivePersonalityKeys = {"AggressiveFollow", "AggressiveJerkAcceleration", "AggressiveJerkDeceleration", "AggressiveJerkDanger", "AggressiveJerkSpeed", "AggressiveJerkSpeedDecrease", "ResetAggressivePersonality"}; + QSet conditionalExperimentalKeys = {"CESpeed", "CESpeedLead", "CECurves", "CELead", "CEModelStopTime", "CESignalSpeed", "CEStopLights", "ShowCEMStatus"}; + QSet curveSpeedKeys = {"CalibratedLateralAcceleration", "CalibrationProgress", "ResetCurveData", "ShowCSCStatus"}; + QSet customDrivingPersonalityKeys = {"AggressivePersonalityProfile", "RelaxedPersonalityProfile", "StandardPersonalityProfile", "TrafficPersonalityProfile"}; + QSet longitudinalTuneKeys = {"AccelerationProfile", "DecelerationProfile", "HumanAcceleration", "HumanFollowing", "HumanLaneChanges", "LeadDetectionThreshold", "TacoTune"}; + QSet qolKeys = {"CustomCruise", "CustomCruiseLong", "ForceStops", "IncreasedStoppedDistance", "MapGears", "ReverseCruise", "SetSpeedOffset", "WeatherPresets"}; + QSet relaxedPersonalityKeys = {"RelaxedFollow", "RelaxedJerkAcceleration", "RelaxedJerkDeceleration", "RelaxedJerkDanger", "RelaxedJerkSpeed", "RelaxedJerkSpeedDecrease", "ResetRelaxedPersonality"}; + QSet speedLimitControllerKeys = {"SLCOffsets", "SLCFallback", "SLCOverride", "SLCPriority", "SLCQOL", "SLCVisuals"}; + QSet speedLimitControllerOffsetsKeys = {"Offset1", "Offset2", "Offset3", "Offset4", "Offset5", "Offset6", "Offset7"}; + QSet speedLimitControllerQOLKeys = {"SetSpeedLimit", "SLCConfirmation", "SLCLookaheadHigher", "SLCLookaheadLower", "SLCMapboxFiller"}; + QSet speedLimitControllerVisualKeys = {"ShowSLCOffset", "SpeedLimitSources"}; + QSet standardPersonalityKeys = {"StandardFollow", "StandardJerkAcceleration", "StandardJerkDeceleration", "StandardJerkDanger", "StandardJerkSpeed", "StandardJerkSpeedDecrease", "ResetStandardPersonality"}; + QSet trafficPersonalityKeys = {"TrafficFollow", "TrafficJerkAcceleration", "TrafficJerkDeceleration", "TrafficJerkDanger", "TrafficJerkSpeed", "TrafficJerkSpeedDecrease", "ResetTrafficPersonality"}; + QSet weatherKeys = {"LowVisibilityOffsets", "RainOffsets", "RainStormOffsets", "SetWeatherKey", "SnowOffsets"}; + QSet weatherLowVisibilityKeys = {"IncreaseFollowingLowVisibility", "IncreasedStoppedDistanceLowVisibility", "ReduceAccelerationLowVisibility", "ReduceLateralAccelerationLowVisibility"}; + QSet weatherRainKeys = {"IncreaseFollowingRain", "IncreasedStoppedDistanceRain", "ReduceAccelerationRain", "ReduceLateralAccelerationRain"}; + QSet weatherRainStormKeys = {"IncreaseFollowingRainStorm", "IncreasedStoppedDistanceRainStorm", "ReduceAccelerationRainStorm", "ReduceLateralAccelerationRainStorm"}; + QSet weatherSnowKeys = {"IncreaseFollowingSnow", "IncreasedStoppedDistanceSnow", "ReduceAccelerationSnow", "ReduceLateralAccelerationSnow"}; + + QSet parentKeys; + + FrogPilotButtonsControl *weatherKeyControl; + + FrogPilotParamValueControl *longitudinalActuatorDelayToggle; + FrogPilotParamValueControl *startAccelToggle; + FrogPilotParamValueControl *stopAccelToggle; + FrogPilotParamValueControl *stoppingDecelRateToggle; + FrogPilotParamValueControl *vEgoStartingToggle; + FrogPilotParamValueControl *vEgoStoppingToggle; + + FrogPilotSettingsWindow *parent; + + LabelControl *calibratedLateralAccelerationLabel; + LabelControl *calibrationProgressLabel; + + Params params; + + QNetworkAccessManager *networkManager; +}; diff --git a/frogpilot/ui/qt/offroad/maps_settings.cc b/frogpilot/ui/qt/offroad/maps_settings.cc new file mode 100644 index 000000000..3e0913411 --- /dev/null +++ b/frogpilot/ui/qt/offroad/maps_settings.cc @@ -0,0 +1,312 @@ +#include + +#include + +#include "frogpilot/ui/qt/offroad/maps_settings.h" + +FrogPilotMapsPanel::FrogPilotMapsPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + QStackedLayout *mapsLayout = new QStackedLayout(); + addItem(mapsLayout); + + FrogPilotListWidget *settingsList = new FrogPilotListWidget(this); + + std::vector scheduleOptions{tr("Manually"), tr("Weekly"), tr("Monthly")}; + preferredSchedule = new ButtonParamControl("PreferredSchedule", tr("Automatically Update Maps"), + tr("How often maps update from \"OpenStreetMap (OSM)\" with the latest speed limit information. " + "Weekly updates run every Sunday; monthly updates run on the 1st."), + "", + scheduleOptions); + settingsList->addItem(preferredSchedule); + + downloadMapsButton = new ButtonControl(tr("Download Maps"), tr("DOWNLOAD"), tr("Manually update your selected map sources so \"Speed Limit Controller\" has the latest speed limit information.")); + QObject::connect(downloadMapsButton, &ButtonControl::clicked, [this] { + if (downloadMapsButton->text() == tr("CANCEL")) { + if (FrogPilotConfirmationDialog::yesorno(tr("Cancel the download?"), this)) { + cancelDownload(); + } + } else { + startDownload(); + } + }); + settingsList->addItem(downloadMapsButton); + + settingsList->addItem(lastMapsDownload = new LabelControl(tr("Last Updated"), params.get("LastMapsUpdate").empty() ? "Never" : QString::fromStdString(params.get("LastMapsUpdate")))); + + selectMaps = new FrogPilotButtonsControl(tr("Map Sources"), + tr("Select the countries or U.S. states to use with \"Speed Limit Controller\".") , + "", {tr("COUNTRIES"), tr("STATES")}); + QObject::connect(selectMaps, &FrogPilotButtonsControl::buttonClicked, [mapsLayout, this](int id) { + mapsLayout->setCurrentIndex(id + 1); + + openSubPanel(); + }); + settingsList->addItem(selectMaps); + + settingsList->addItem(downloadStatus = new LabelControl(tr("Progress"))); + settingsList->addItem(downloadTimeElapsed = new LabelControl(tr("Time Elapsed"))); + settingsList->addItem(downloadETA = new LabelControl(tr("Time Remaining"))); + + downloadETA->setVisible(false); + downloadStatus->setVisible(false); + downloadTimeElapsed->setVisible(false); + + removeMapsButton = new ButtonControl(tr("Remove Maps"), tr("REMOVE"), tr("Delete downloaded map data to free up storage space.")); + QObject::connect(removeMapsButton, &ButtonControl::clicked, [this] { + if (FrogPilotConfirmationDialog::yesorno(tr("Delete all downloaded maps?"), this)) { + std::thread([this] { + mapsSize->setText(tr("0 MB")); + + mapsFolderPath.removeRecursively(); + }).detach(); + } + }); + settingsList->addItem(removeMapsButton); + + resetMapdButton = new ButtonControl(tr("Reset Downloader"), tr("RESET"), + tr("Reset the map downloader. Use this if downloads are stuck or failing.")); + QObject::connect(resetMapdButton, &ButtonControl::clicked, [parent, this]() { + if (ConfirmationDialog::confirm(tr("Reset the map downloader? Your device will reboot afterward."), tr("Reset"), this)) { + std::thread([parent, this]() { + parent->keepScreenOn = true; + + resetMapdButton->setEnabled(false); + resetMapdButton->setValue(tr("Resetting...")); + + std::system("pkill mapd"); + + QDir("/data/media/0/osm").removeRecursively(); + + resetMapdButton->setValue(tr("Reset!")); + + util::sleep_for(2500); + + resetMapdButton->setValue(tr("Rebooting...")); + + util::sleep_for(2500); + + Hardware::reboot(); + }).detach(); + } + }); + settingsList->addItem(resetMapdButton); + + settingsList->addItem(mapsSize = new LabelControl(tr("Storage Used"), calculateDirectorySize(mapsFolderPath))); + + ScrollView *settingsPanel = new ScrollView(settingsList, this); + mapsLayout->addWidget(settingsPanel); + + FrogPilotListWidget *countriesList = new FrogPilotListWidget(this); + std::vector>> countries = { + {tr("Africa"), africaMap}, + {tr("Antarctica"), antarcticaMap}, + {tr("Asia"), asiaMap}, + {tr("Europe"), europeMap}, + {tr("North America"), northAmericaMap}, + {tr("Oceania"), oceaniaMap}, + {tr("South America"), southAmericaMap} + }; + + for (std::pair> country : countries) { + countriesList->addItem(new LabelControl(country.first, "")); + countriesList->addItem(new MapSelectionControl(country.second, true)); + } + + ScrollView *countryMapsPanel = new ScrollView(countriesList, this); + mapsLayout->addWidget(countryMapsPanel); + + FrogPilotListWidget *statesList = new FrogPilotListWidget(this); + std::vector>> states = { + {tr("United States - Midwest"), midwestMap}, + {tr("United States - Northeast"), northeastMap}, + {tr("United States - South"), southMap}, + {tr("United States - West"), westMap}, + {tr("United States - Territories"), territoriesMap} + }; + + for (std::pair> state : states) { + statesList->addItem(new LabelControl(state.first, "")); + statesList->addItem(new MapSelectionControl(state.second)); + } + + ScrollView *stateMapsPanel = new ScrollView(statesList, this); + mapsLayout->addWidget(stateMapsPanel); + + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [mapsLayout, settingsPanel, this] { + if (forceOpenDescriptions) { + downloadMapsButton->showDescription(); + preferredSchedule->showDescription(); + removeMapsButton->showDescription(); + resetMapdButton->showDescription(); + selectMaps->showDescription(); + } + + std::string mapsSelected = params.get("MapsSelected"); + hasMapsSelected = !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("nations").toArray().isEmpty(); + hasMapsSelected |= !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("states").toArray().isEmpty(); + + mapsLayout->setCurrentWidget(settingsPanel); + }); + QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotMapsPanel::updateState); +} + +void FrogPilotMapsPanel::showEvent(QShowEvent *event) { + if (forceOpenDescriptions) { + downloadMapsButton->showDescription(); + preferredSchedule->showDescription(); + removeMapsButton->showDescription(); + resetMapdButton->showDescription(); + selectMaps->showDescription(); + } + + UIState &s = *uiState(); + UIScene &scene = s.scene; + + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + std::string mapsSelected = params.get("MapsSelected"); + hasMapsSelected = !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("nations").toArray().isEmpty(); + hasMapsSelected |= !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("states").toArray().isEmpty(); + + bool parked = !scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo; + + removeMapsButton->setVisible(mapsFolderPath.exists()); + + std::string osmDownloadProgress = params.get("OSMDownloadProgress"); + if (!osmDownloadProgress.empty()) { + downloadMapsButton->setText(tr("CANCEL")); + downloadStatus->setText(tr("Calculating...")); + + downloadStatus->setVisible(true); + + lastMapsDownload->setVisible(false); + removeMapsButton->setVisible(false); + resetMapdButton->setVisible(false); + + updateDownloadLabels(osmDownloadProgress); + } else { + downloadMapsButton->setEnabled(!cancellingDownload && hasMapsSelected && frogpilot_scene.online && parked); + downloadMapsButton->setValue(frogpilot_scene.online ? (parked ? "" : tr("Not parked")) : tr("Offline...")); + } +} + + +void FrogPilotMapsPanel::updateState(const UIState &s, const FrogPilotUIState &fs) { + if (!isVisible() || s.sm->frame % (UI_FREQ / 2) != 0) { + return; + } + + const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + const UIScene &scene = s.scene; + + bool parked = !scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo; + + std::string osmDownloadProgress = params.get("OSMDownloadProgress"); + if (!osmDownloadProgress.empty() && !cancellingDownload) { + updateDownloadLabels(osmDownloadProgress); + } else { + downloadMapsButton->setEnabled(!cancellingDownload && hasMapsSelected && frogpilot_scene.online && parked); + downloadMapsButton->setValue(frogpilot_scene.online ? (parked ? "" : tr("Not parked")) : tr("Offline...")); + } + + parent->keepScreenOn = !osmDownloadProgress.empty(); +} + +void FrogPilotMapsPanel::cancelDownload() { + cancellingDownload = true; + + downloadMapsButton->setEnabled(false); + + downloadETA->setText(tr("Calculating...")); + downloadMapsButton->setText(tr("CANCEL")); + downloadStatus->setText(tr("Calculating...")); + downloadTimeElapsed->setText(tr("Calculating...")); + + params.remove("OSMDownloadProgress"); + params_memory.remove("OSMDownloadLocations"); + + std::system("pkill mapd"); + + QTimer::singleShot(2500, [this]() { + cancellingDownload = false; + + downloadMapsButton->setEnabled(true); + + downloadMapsButton->setText(tr("DOWNLOAD")); + + downloadETA->setVisible(false); + downloadStatus->setVisible(false); + downloadTimeElapsed->setVisible(false); + + lastMapsDownload->setVisible(true); + removeMapsButton->setVisible(mapsFolderPath.exists()); + resetMapdButton->setVisible(true); + + update(); + }); +} + +void FrogPilotMapsPanel::startDownload() { + downloadETA->setText(tr("Calculating...")); + downloadMapsButton->setText(tr("CANCEL")); + downloadStatus->setText(tr("Calculating...")); + downloadTimeElapsed->setText(tr("Calculating...")); + + downloadETA->setVisible(true); + downloadStatus->setVisible(true); + downloadTimeElapsed->setVisible(true); + + lastMapsDownload->setVisible(false); + removeMapsButton->setVisible(false); + resetMapdButton->setVisible(false); + + elapsedTime.start(); + startTime = QDateTime::currentDateTime(); + + params_memory.put("OSMDownloadLocations", params.get("MapsSelected")); +} + +void FrogPilotMapsPanel::updateDownloadLabels(std::string &osmDownloadProgress) { + static std::regex fileStatusRegex(R"("total_files":(\d+),.*"downloaded_files":(\d+))"); + + std::smatch match; + if (std::regex_search(osmDownloadProgress, match, fileStatusRegex)) { + int totalFiles = std::stoi(match[1].str()); + int downloadedFiles = std::stoi(match[2].str()); + + if (downloadedFiles == totalFiles) { + downloadMapsButton->setText(tr("DOWNLOAD")); + lastMapsDownload->setText(formatCurrentDate()); + + downloadETA->setVisible(false); + downloadStatus->setVisible(false); + downloadTimeElapsed->setVisible(false); + + lastMapsDownload->setVisible(true); + removeMapsButton->setVisible(true); + resetMapdButton->setVisible(true); + + params.put("LastMapsUpdate", formatCurrentDate().toStdString()); + params.remove("OSMDownloadProgress"); + + update(); + + return; + } + + static int previousDownloadedFiles = 0; + if (downloadedFiles != previousDownloadedFiles) { + std::thread([this]() { + mapsSize->setText(calculateDirectorySize(mapsFolderPath)); + }).detach(); + } + + downloadETA->setText(QString("%1").arg(formatETA(elapsedTime.elapsed(), downloadedFiles, previousDownloadedFiles, totalFiles, startTime))); + downloadStatus->setText(QString("%1 / %2 (%3%)").arg(downloadedFiles).arg(totalFiles).arg((downloadedFiles * 100) / totalFiles)); + downloadTimeElapsed->setText(formatElapsedTime(elapsedTime.elapsed())); + + previousDownloadedFiles = downloadedFiles; + } +} diff --git a/frogpilot/ui/qt/offroad/maps_settings.h b/frogpilot/ui/qt/offroad/maps_settings.h new file mode 100644 index 000000000..8e3e91447 --- /dev/null +++ b/frogpilot/ui/qt/offroad/maps_settings.h @@ -0,0 +1,51 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" +#include "frogpilot/ui/qt/widgets/navigation_functions.h" + +class FrogPilotMapsPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotMapsPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +signals: + void openSubPanel(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void cancelDownload(); + void startDownload(); + void updateDownloadLabels(std::string &osmDownloadProgress); + void updateState(const UIState &s, const FrogPilotUIState &fs); + + bool cancellingDownload; + bool forceOpenDescriptions; + bool hasMapsSelected; + + ButtonControl *downloadMapsButton; + ButtonControl *removeMapsButton; + ButtonControl *resetMapdButton; + + ButtonParamControl *preferredSchedule; + + FrogPilotButtonsControl *selectMaps; + + FrogPilotSettingsWindow *parent; + + LabelControl *downloadETA; + LabelControl *downloadStatus; + LabelControl *downloadTimeElapsed; + LabelControl *lastMapsDownload; + LabelControl *mapsSize; + + Params params; + + QDateTime startTime; + + QDir mapsFolderPath{"/data/media/0/osm/offline"}; + + QElapsedTimer elapsedTime; +}; diff --git a/frogpilot/ui/qt/offroad/model_settings.cc b/frogpilot/ui/qt/offroad/model_settings.cc new file mode 100644 index 000000000..a42fae7cf --- /dev/null +++ b/frogpilot/ui/qt/offroad/model_settings.cc @@ -0,0 +1,611 @@ +#include "frogpilot/ui/qt/offroad/model_settings.h" + +bool hasAllTinygradFiles(const QDir &modelDir, const QString &modelKey) { + QStringList tinygradSuffixes = { + "_driving_policy_metadata.pkl", + "_driving_policy_tinygrad.pkl", + "_driving_vision_metadata.pkl", + "_driving_vision_tinygrad.pkl" + }; + + for (const QString &suffix : tinygradSuffixes) { + if (!modelDir.exists(modelKey + suffix)) { + return false; + } + } + return true; +} + +FrogPilotModelPanel::FrogPilotModelPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + defaultModel = QString::fromStdString(params.getKeyDefaultValue("DrivingModel").value()); + + QStackedLayout *modelLayout = new QStackedLayout(); + addItem(modelLayout); + + FrogPilotListWidget *modelList = new FrogPilotListWidget(this); + + ScrollView *modelPanel = new ScrollView(modelList, this); + + modelLayout->addWidget(modelPanel); + + FrogPilotListWidget *modelLabelsList = new FrogPilotListWidget(this); + + ScrollView *modelLabelsPanel = new ScrollView(modelLabelsList, this); + + modelLayout->addWidget(modelLabelsPanel); + + const std::vector> modelToggles { + {"AutomaticallyDownloadModels", tr("Automatically Download New Models"), tr("Automatically download new driving models as they become available."), ""}, + {"DeleteModel", tr("Delete Driving Models"), tr("Delete downloaded driving models to free up storage space."), ""}, + {"DownloadModel", tr("Download Driving Models"), tr("Manually download driving models to the device."), ""}, + {"ModelRandomizer", tr("Model Randomizer"), tr("Select a random driving model each drive and use feedback prompts at the end of the drive to help find the model that best suits you!"), ""}, + {"ManageBlacklistedModels", tr("Manage Model Blacklist"), tr("Add or remove driving models from the \"Model Randomizer\" blacklist."), ""}, + {"ManageScores", tr("Manage Model Ratings"), tr("View or reset saved model ratings used by the \"Model Randomizer\"."), ""}, + {"SelectModel", tr("Select Driving Model"), tr("Choose which driving model openpilot uses."), ""}, + {"UpdateTinygrad", tr("Update Model Manager"), tr("Update the \"Model Manager\" to support the latest models."), ""} + }; + + for (const auto &[param, title, desc, icon] : modelToggles) { + AbstractControl *modelToggle; + + if (param == "DeleteModel") { + deleteModelButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DELETE ALL")}); + QObject::connect(deleteModelButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + QStringList deletableModels; + for (const QString &file : modelDir.entryList(QDir::Files)) { + QString base = QFileInfo(file).baseName(); + for (const QString &modelKey : modelFileToNameMapProcessed.keys()) { + if (base.startsWith(modelKey)) { + QString modelName = modelFileToNameMapProcessed.value(modelKey); + if (!deletableModels.contains(modelName)) { + deletableModels.append(modelName); + } + } + } + } + deletableModels.removeAll(cleanModelName(currentModel)); + deletableModels.removeAll(modelFileToNameMapProcessed.value(cleanModelName(defaultModel))); + noModelsDownloaded = deletableModels.isEmpty(); + + if (id == 0) { + QString modelToDelete = MultiOptionDialog::getSelection(tr("Select a driving model to delete"), deletableModels, "", this); + if (!modelToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to delete the \"%1\" model?").arg(modelToDelete), tr("Delete"), this)) { + QString modelFile = modelFileToNameMapProcessed.key(modelToDelete); + for (const QString &file : modelDir.entryList(QDir::Files)) { + QString base = QFileInfo(file).baseName(); + if (base.startsWith(modelFile)) { + QFile::remove(modelDir.filePath(file)); + } + } + + noModelsDownloaded = deletableModels.isEmpty(); + + deletableModels.removeAll(modelToDelete); + deleteModelButton->setEnabled(!noModelsDownloaded); + + allModelsDownloaded = false; + } + } else if (id == 1) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to delete all of your downloaded driving models?"), tr("Delete"), this)) { + for (const QString &file : modelDir.entryList(QDir::Files)) { + QString base = QFileInfo(file).baseName(); + for (const QString &modelKey : modelFileToNameMapProcessed.keys()) { + QString modelName = modelFileToNameMapProcessed.value(modelKey); + if (deletableModels.contains(modelName) && base.startsWith(modelKey)) { + QFile::remove(modelDir.filePath(file)); + break; + } + } + } + + deleteModelButton->setEnabled(false); + + allModelsDownloaded = false; + noModelsDownloaded = true; + } + } + }); + modelToggle = deleteModelButton; + } else if (param == "DownloadModel") { + downloadModelButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DOWNLOAD"), tr("DOWNLOAD ALL")}); + QObject::connect(downloadModelButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + if (tinygradUpdate) { + if (FrogPilotConfirmationDialog::yesorno(tr("Tinygrad is out of date and must be updated before you can download new models. Update now?"), this)) { + if (FrogPilotConfirmationDialog::yesorno(tr("Updating Tinygrad will delete all existing Tinygrad-based models which will need to be re-downloaded. Proceed?"), this)) { + params_memory.putBool("UpdateTinygrad", true); + params_memory.put("ModelDownloadProgress", "Downloading..."); + + updateTinygradButton->setText(0, tr("CANCEL")); + updateTinygradButton->setValue(tr("Updating...")); + + updatingTinygrad = true; + } + } + } else if (id == 0) { + if (modelDownloading) { + params_memory.putBool("CancelModelDownload", true); + + cancellingDownload = true; + } else { + QStringList downloadableModels = availableModelNames; + for (const QString &modelKey : modelFileToNameMap.keys()) { + QString modelName = modelFileToNameMap.value(modelKey); + if (hasAllTinygradFiles(modelDir, modelKey)) { + downloadableModels.removeAll(modelName); + } + } + allModelsDownloaded = downloadableModels.isEmpty(); + + QString modelToDownload = MultiOptionDialog::getSelection(tr("Select a driving model to download"), downloadableModels, "", this); + if (!modelToDownload.isEmpty()) { + params_memory.put("ModelToDownload", modelFileToNameMap.key(modelToDownload).toStdString()); + params_memory.put("ModelDownloadProgress", "Downloading..."); + + downloadModelButton->setText(0, tr("CANCEL")); + + downloadModelButton->setValue(tr("Downloading...")); + + downloadModelButton->setVisibleButton(1, false); + + modelDownloading = true; + } + } + } else if (id == 1) { + if (allModelsDownloading) { + params_memory.putBool("CancelModelDownload", true); + + cancellingDownload = true; + } else { + params_memory.putBool("DownloadAllModels", true); + params_memory.put("ModelDownloadProgress", "Downloading..."); + + downloadModelButton->setText(1, tr("CANCEL")); + + downloadModelButton->setValue(tr("Downloading...")); + + downloadModelButton->setVisibleButton(0, false); + + allModelsDownloading = true; + } + } + }); + modelToggle = downloadModelButton; + } else if (param == "ManageBlacklistedModels") { + FrogPilotButtonsControl *blacklistButton = new FrogPilotButtonsControl(title, desc, icon, {tr("ADD"), tr("REMOVE"), tr("REMOVE ALL")}); + QObject::connect(blacklistButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + QStringList blacklistedModels = QString::fromStdString(params.get("BlacklistedModels")).split(","); + blacklistedModels.removeAll(""); + + if (id == 0) { + QStringList blacklistableModels; + for (const QString &model : modelFileToNameMapProcessed.keys()) { + if (!blacklistedModels.contains(model)) { + blacklistableModels.append(modelFileToNameMapProcessed.value(model)); + } + } + + if (blacklistableModels.size() <= 1) { + ConfirmationDialog::alert(tr("There are no more driving models to blacklist. The only available model is \"%1\"!").arg(blacklistableModels.first()), this); + } else { + QString modelToBlacklist = MultiOptionDialog::getSelection(tr("Select a driving model to add to the blacklist"), blacklistableModels, "", this); + if (!modelToBlacklist.isEmpty()) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to add the \"%1\" model to the blacklist?").arg(modelToBlacklist), tr("Add"), this)) { + blacklistedModels.append(modelFileToNameMapProcessed.key(modelToBlacklist)); + + params.put("BlacklistedModels", blacklistedModels.join(",").toStdString()); + } + } + } + } else if (id == 1) { + QStringList whitelistableModels; + for (const QString &model : blacklistedModels) { + QString modelName = modelFileToNameMapProcessed.value(model); + whitelistableModels.append(modelName); + } + whitelistableModels.sort(); + + QString modelToWhitelist = MultiOptionDialog::getSelection(tr("Select a driving model to remove from the blacklist"), whitelistableModels, "", this); + if (!modelToWhitelist.isEmpty()) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to remove the \"%1\" model from the blacklist?").arg(modelToWhitelist), tr("Remove"), this)) { + blacklistedModels.removeAll(modelFileToNameMapProcessed.key(modelToWhitelist)); + + params.put("BlacklistedModels", blacklistedModels.join(",").toStdString()); + } + } + } else if (id == 2) { + if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to remove all of your blacklisted driving models?"), this)) { + params.remove("BlacklistedModels"); + } + } + }); + modelToggle = blacklistButton; + } else if (param == "ManageScores") { + FrogPilotButtonsControl *manageScoresButton = new FrogPilotButtonsControl(title, desc, icon, {tr("RESET"), tr("VIEW")}); + QObject::connect(manageScoresButton, &FrogPilotButtonsControl::buttonClicked, [modelLayout, modelLabelsList, modelLabelsPanel, this](int id) { + if (id == 0) { + if (FrogPilotConfirmationDialog::yesorno(tr("Reset all model drives and ratings? This clears your drive history and collected feedback!"), this)) { + params.remove("ModelDrivesAndScores"); + } + } else if (id == 1) { + openSubPanel(); + + updateModelLabels(modelLabelsList); + + modelLayout->setCurrentWidget(modelLabelsPanel); + } + }); + modelToggle = manageScoresButton; + } else if (param == "SelectModel") { + selectModelButton = new ButtonControl(title, tr("SELECT"), desc); + QObject::connect(selectModelButton, &ButtonControl::clicked, [this]() { + QStringList selectableModels; + for (const QString &modelKey : modelFileToNameMap.keys()) { + if (modelKey != cleanModelName(defaultModel) && hasAllTinygradFiles(modelDir, modelKey)) { + selectableModels.append(modelFileToNameMap.value(modelKey)); + } + } + selectableModels.sort(); + selectableModels.prepend(modelFileToNameMap.value(cleanModelName(defaultModel)) + " (Default)"); + + QString currentModelDisplay = currentModel; + if (currentModel == modelFileToNameMap.value(cleanModelName(defaultModel))) { + currentModelDisplay += " (Default)"; + } + + QString modelToSelect = MultiOptionDialog::getSelection(tr("Select a Model"), selectableModels, currentModelDisplay, this); + if (!modelToSelect.isEmpty()) { + modelToSelect.remove(" (Default)"); + currentModel = modelToSelect; + + params.put("DrivingModel", modelFileToNameMap.key(modelToSelect).toStdString()); + + updateFrogPilotToggles(); + + if (started) { + if (FrogPilotConfirmationDialog::toggleReboot(this)) { + Hardware::reboot(); + } + } + selectModelButton->setValue(modelToSelect); + + QStringList deletableModels; + for (const QString &file : modelDir.entryList(QDir::Files)) { + QString base = QFileInfo(file).baseName(); + for (const QString &modelKey : modelFileToNameMapProcessed.keys()) { + if (base.startsWith(modelKey)) { + QString modelName = modelFileToNameMapProcessed.value(modelKey); + if (!deletableModels.contains(modelName)) { + deletableModels.append(modelName); + } + } + } + } + deletableModels.removeAll(cleanModelName(currentModel)); + deletableModels.removeAll(modelFileToNameMapProcessed.value(cleanModelName(defaultModel))); + noModelsDownloaded = deletableModels.isEmpty(); + } + }); + modelToggle = selectModelButton; + + } else if (param == "UpdateTinygrad") { + updateTinygradButton = new FrogPilotButtonsControl(title, desc, icon, {tr("UPDATE")}); + QObject::connect(updateTinygradButton, &FrogPilotButtonsControl::buttonClicked, [this]() { + if (updatingTinygrad) { + params_memory.putBool("CancelModelDownload", true); + + updateTinygradButton->setEnabled(false); + updateTinygradButton->setValue(tr("Cancelling...")); + + cancellingDownload = true; + } else { + if (FrogPilotConfirmationDialog::yesorno(tr("Updating Tinygrad will delete existing Tinygrad-based driving models and need to be re-downloaded. Proceed?"), this)) { + params_memory.putBool("UpdateTinygrad", true); + params_memory.put("ModelDownloadProgress", "Downloading..."); + + updateTinygradButton->setText(0, tr("CANCEL")); + updateTinygradButton->setValue(tr("Updating...")); + + updatingTinygrad = true; + } + } + }); + modelToggle = updateTinygradButton; + + } else { + modelToggle = new ParamControl(param, title, desc, icon); + } + + toggles[param] = modelToggle; + + modelList->addItem(modelToggle); + + QObject::connect(modelToggle, &AbstractControl::hideDescriptionEvent, [this]() { + update(); + }); + QObject::connect(modelToggle, &AbstractControl::showDescriptionEvent, [this]() { + update(); + }); + } + + openDescriptions(forceOpenDescriptions, toggles); + + QObject::connect(static_cast(toggles["ModelRandomizer"]), &ToggleControl::toggleFlipped, [this](bool state) { + updateToggles(); + + if (state && !allModelsDownloaded) { + if (FrogPilotConfirmationDialog::yesorno(tr("The \"Model Randomizer\" works only with downloaded models. Download all models now?"), this)) { + params_memory.putBool("DownloadAllModels", true); + params_memory.put("ModelDownloadProgress", "Downloading..."); + + downloadModelButton->setValue(tr("Downloading...")); + + allModelsDownloading = true; + } + } + }); + + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [modelLayout, modelPanel, this] { + openDescriptions(forceOpenDescriptions, toggles); + modelLayout->setCurrentWidget(modelPanel); + }); + QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotModelPanel::updateState); +} + +void FrogPilotModelPanel::showEvent(QShowEvent *event) { + UIState &s = *uiState(); + + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + allModelsDownloading = params_memory.getBool("DownloadAllModels"); + modelDownloading = !params_memory.get("ModelDownloadProgress").empty(); + tinygradUpdate = params.getBool("TinygradUpdateAvailable"); + updatingTinygrad = params_memory.getBool("UpdateTinygrad"); + + modelDownloading &= !updatingTinygrad; + + QStringList availableModels = QString::fromStdString(params.get("AvailableModels")).split(","); + availableModels.sort(); + availableModelNames = QString::fromStdString(params.get("AvailableModelNames")).split(","); + availableModelNames.sort(); + + modelFileToNameMap.clear(); + modelFileToNameMapProcessed.clear(); + for (int i = 0; i < qMin(availableModels.size(), availableModelNames.size()); ++i) { + modelFileToNameMap.insert(availableModels[i], availableModelNames[i]); + modelFileToNameMapProcessed.insert(availableModels[i], cleanModelName(availableModelNames[i])); + } + + QStringList downloadableModels = availableModelNames; + for (const QString &modelKey : modelFileToNameMap.keys()) { + QString modelName = modelFileToNameMap.value(modelKey); + if (hasAllTinygradFiles(modelDir, modelKey)) { + downloadableModels.removeAll(modelName); + } + } + allModelsDownloaded = downloadableModels.isEmpty(); + + QStringList deletableModels; + for (const QString &file : modelDir.entryList(QDir::Files)) { + QString base = QFileInfo(file).baseName(); + for (const QString &modelKey : modelFileToNameMapProcessed.keys()) { + if (base.startsWith(modelKey)) { + QString modelName = modelFileToNameMapProcessed.value(modelKey); + if (!deletableModels.contains(modelName)) { + deletableModels.append(modelName); + } + } + } + } + deletableModels.removeAll(cleanModelName(currentModel)); + deletableModels.removeAll(modelFileToNameMapProcessed.value(cleanModelName(defaultModel))); + noModelsDownloaded = deletableModels.isEmpty(); + + QString modelKey = cleanModelName(QString::fromStdString(params.get("DrivingModel"))); + if (!hasAllTinygradFiles(modelDir, modelKey)) { + modelKey = defaultModel; + } + currentModel = modelFileToNameMap.value(modelKey); + + QString currentModelName = currentModel; + if (modelKey == defaultModel) { + currentModelName.append(" (Default)"); + } + selectModelButton->setValue(currentModelName); + + bool parked = !s.scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo; + + deleteModelButton->setEnabled(!(allModelsDownloading || modelDownloading || noModelsDownloaded)); + + downloadModelButton->setEnabledButtons(0, !allModelsDownloaded && !allModelsDownloading && !cancellingDownload && !updatingTinygrad && frogpilot_scene.online && parked); + downloadModelButton->setEnabledButtons(1, !allModelsDownloaded && !modelDownloading && !cancellingDownload && !updatingTinygrad && frogpilot_scene.online && parked); + + downloadModelButton->setValue(frogpilot_scene.online ? (parked ? "" : "Not parked") : tr("Offline...")); + + updateTinygradButton->setEnabled(!modelDownloading && !cancellingDownload && frogpilot_scene.online && parked && tinygradUpdate); + updateTinygradButton->setValue(tinygradUpdate ? tr("Update available!") : tr("Up to date!")); + + started = s.scene.started; + + updateToggles(); +} + +void FrogPilotModelPanel::updateState(const UIState &s, const FrogPilotUIState &fs) { + if (!isVisible() || finalizingDownload) { + return; + } + + const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + bool parked = !started || frogpilot_scene.parked || parent->isFrogsGoMoo; + + if (allModelsDownloading || modelDownloading) { + QString progress = QString::fromStdString(params_memory.get("ModelDownloadProgress")); + bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|failed|missing|offline", QRegularExpression::CaseInsensitiveOption)); + + { + QString translatedProgress; + if (progress == "Downloading...") { + translatedProgress = tr("Downloading..."); + } else if (progress == "Downloaded!") { + translatedProgress = tr("Downloaded!"); + } else if (progress == "All models downloaded!") { + translatedProgress = tr("All models downloaded!"); + } else if (progress.contains("cancelled", Qt::CaseInsensitive)) { + translatedProgress = tr("Download cancelled..."); + } else if (progress.contains("failed", Qt::CaseInsensitive)) { + translatedProgress = tr("Download failed..."); + } else if (progress.contains("offline", Qt::CaseInsensitive)) { + translatedProgress = tr("GitHub and GitLab are offline..."); + } else if (progress == "Repository unavailable") { + translatedProgress = tr("Repository unavailable"); + } else { + translatedProgress = progress; + } + downloadModelButton->setValue(translatedProgress); + } + + if (progress == "All models downloaded!" || progress == "Downloaded!" && !allModelsDownloading || downloadFailed) { + finalizingDownload = true; + + QTimer::singleShot(2500, [progress, this]() { + allModelsDownloading = false; + cancellingDownload = false; + finalizingDownload = false; + modelDownloading = false; + noModelsDownloaded = false; + + QStringList downloadableModels = availableModelNames; + for (const QString &modelKey : modelFileToNameMap.keys()) { + QString modelName = modelFileToNameMap.value(modelKey); + if (hasAllTinygradFiles(modelDir, modelKey)) { + downloadableModels.removeAll(modelName); + } + } + allModelsDownloaded = downloadableModels.isEmpty(); + + params_memory.remove("ModelDownloadProgress"); + + downloadModelButton->setEnabled(true); + downloadModelButton->setValue(""); + }); + } + } else { + downloadModelButton->setValue(frogpilot_scene.online ? (parked ? "" : "Not parked") : tr("Offline...")); + } + + if (updatingTinygrad) { + QString progress = QString::fromStdString(params_memory.get("ModelDownloadProgress")); + bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|failed|missing|offline", QRegularExpression::CaseInsensitiveOption)); + + { + QString translatedProgress; + if (progress == "Downloading...") { + translatedProgress = tr("Downloading..."); + } else if (progress == "Downloaded!") { + translatedProgress = tr("Downloaded!"); + } else if (progress == "All models downloaded!") { + translatedProgress = tr("All models downloaded!"); + } else if (progress.contains("cancelled", Qt::CaseInsensitive)) { + translatedProgress = tr("Download cancelled..."); + } else if (progress.contains("failed", Qt::CaseInsensitive)) { + translatedProgress = tr("Download failed..."); + } else if (progress.contains("offline", Qt::CaseInsensitive)) { + translatedProgress = tr("GitHub and GitLab are offline..."); + } else if (progress == "Repository unavailable") { + translatedProgress = tr("Repository unavailable"); + } else { + translatedProgress = progress; + } + updateTinygradButton->setValue(translatedProgress); + } + + if (progress == "Updated!" && updatingTinygrad || downloadFailed) { + finalizingDownload = true; + + QTimer::singleShot(2500, [progress, this]() { + modelDownloading = !params_memory.get("ModelDownloadProgress").empty(); + + if (modelDownloading) { + downloadModelButton->setText(1, tr("CANCEL")); + + downloadModelButton->setValue(tr("Downloading...")); + + downloadModelButton->setVisibleButton(0, false); + } else { + cancellingDownload = false; + } + + tinygradUpdate = params.getBool("TinygradUpdateAvailable"); + + finalizingDownload = false; + updatingTinygrad = false; + + updateTinygradButton->setEnabled(tinygradUpdate); + updateTinygradButton->setText(0, tr("UPDATE")); + updateTinygradButton->setValue(tinygradUpdate ? tr("Update available!") : tr("Up to date!")); + }); + } + } + + deleteModelButton->setEnabled(!(allModelsDownloading || modelDownloading || noModelsDownloaded)); + + downloadModelButton->setText(0, modelDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + downloadModelButton->setText(1, allModelsDownloading ? tr("CANCEL") : tr("DOWNLOAD ALL")); + + downloadModelButton->setEnabledButtons(0, !allModelsDownloaded && !allModelsDownloading && !cancellingDownload && !finalizingDownload && !updatingTinygrad && frogpilot_scene.online && parked); + downloadModelButton->setEnabledButtons(1, !allModelsDownloaded && !modelDownloading && !cancellingDownload && !finalizingDownload && !updatingTinygrad && frogpilot_scene.online && parked); + + downloadModelButton->setVisibleButton(0, !allModelsDownloading); + downloadModelButton->setVisibleButton(1, !modelDownloading); + + updateTinygradButton->setEnabled(!modelDownloading && !cancellingDownload && !cancellingDownload && !finalizingDownload && frogpilot_scene.online && parked && tinygradUpdate); + + started = s.scene.started; + + parent->keepScreenOn = allModelsDownloading || modelDownloading || updatingTinygrad; +} + +void FrogPilotModelPanel::updateModelLabels(FrogPilotListWidget *labelsList) { + labelsList->clear(); + + QJsonObject modelDrivesAndScores = QJsonDocument::fromJson(QString::fromStdString(params.get("ModelDrivesAndScores")).toUtf8()).object(); + + for (const QString &modelName : availableModelNames) { + QJsonObject modelData = modelDrivesAndScores.value(cleanModelName(modelName)).toObject(); + + int drives = modelData.value("Drives").toInt(0); + int score = modelData.value("Score").toInt(0); + + QString drivesDisplay = drives == 1 ? QString("%1 Drive").arg(drives) : drives > 0 ? QString("%1 Drives").arg(drives) : "N/A"; + QString scoreDisplay = drives > 0 ? QString("Score: %1%").arg(score) : "N/A"; + + QString labelTitle = cleanModelName(modelName); + QString labelText = QString("%1 (%2)").arg(scoreDisplay, drivesDisplay); + + LabelControl *labelControl = new LabelControl(labelTitle, labelText, "", this); + labelsList->addItem(labelControl); + } +} + +void FrogPilotModelPanel::updateToggles() { + for (auto &[key, toggle] : toggles) { + bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble(); + + if (key == "ManageBlacklistedModels" || key == "ManageScores") { + setVisible &= params.getBool("ModelRandomizer"); + } + + else if (key == "SelectModel") { + setVisible &= !params.getBool("ModelRandomizer"); + } + + toggle->setVisible(setVisible); + } + + openDescriptions(forceOpenDescriptions, toggles); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/model_settings.h b/frogpilot/ui/qt/offroad/model_settings.h new file mode 100644 index 000000000..d35370e0d --- /dev/null +++ b/frogpilot/ui/qt/offroad/model_settings.h @@ -0,0 +1,54 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotModelPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotModelPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +signals: + void openSubPanel(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void updateModelLabels(FrogPilotListWidget *labelsList); + void updateState(const UIState &s, const FrogPilotUIState &fs); + void updateToggles(); + + bool allModelsDownloaded; + bool allModelsDownloading; + bool cancellingDownload; + bool finalizingDownload; + bool forceOpenDescriptions; + bool modelDownloading; + bool noModelsDownloaded; + bool started; + bool tinygradUpdate; + bool updatingTinygrad; + + std::map toggles; + + ButtonControl *selectModelButton; + + FrogPilotButtonsControl *deleteModelButton; + FrogPilotButtonsControl *downloadModelButton; + FrogPilotButtonsControl *updateTinygradButton; + + FrogPilotSettingsWindow *parent; + + Params params; + + QDir modelDir{"/data/models/"}; + + QMap modelFileToNameMap; + QMap modelFileToNameMapProcessed; + + QString currentModel; + QString defaultModel; + + QStringList availableModelNames; +}; diff --git a/frogpilot/ui/qt/offroad/navigation_settings.cc b/frogpilot/ui/qt/offroad/navigation_settings.cc new file mode 100644 index 000000000..4f787858c --- /dev/null +++ b/frogpilot/ui/qt/offroad/navigation_settings.cc @@ -0,0 +1,330 @@ +#include "frogpilot/ui/qt/offroad/navigation_settings.h" + +FrogPilotNavigationPanel::FrogPilotNavigationPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + networkManager = new QNetworkAccessManager(this); + + primelessLayout = new QStackedLayout(); + addItem(primelessLayout); + + FrogPilotListWidget *settingsList = new FrogPilotListWidget(this); + ipLabel = new LabelControl(tr("Manage Your Settings At"), tr("Offline...")); + settingsList->addItem(ipLabel); + + publicMapboxKeyControl = new FrogPilotButtonsControl(tr("Public Mapbox Key"), tr("Manage your Public Mapbox Key."), "", {tr("ADD"), tr("TEST")}); + QObject::connect(publicMapboxKeyControl, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + if (id == 0) { + if (mapboxPublicKeySet) { + if (FrogPilotConfirmationDialog::yesorno(tr("Remove your Public Mapbox Key?"), this)) { + params.remove("MapboxPublicKey"); + + updateButtons(); + } + } else { + int minKeyLength = 80; + QString key = InputDialog::getText(tr("Enter your Public Mapbox Key"), this, "", false, minKeyLength).trimmed(); + if (!key.isEmpty()) { + if (!key.startsWith("pk.")) { + key = "pk." + key; + } + params.put("MapboxPublicKey", key.toStdString()); + updateButtons(); + } + } + } else { + publicMapboxKeyControl->setValue(tr("Testing...")); + + QString key = QString::fromStdString(params.get("MapboxPublicKey")); + QString url = QString("https://api.mapbox.com/geocoding/v5/mapbox.places/mapbox.json?access_token=%1").arg(key); + + QNetworkRequest request(url); + QNetworkReply *reply = networkManager->get(request); + connect(reply, &QNetworkReply::finished, [=]() { + publicMapboxKeyControl->setValue(""); + + QString message; + if (reply->error() == QNetworkReply::NoError) { + message = tr("Key is valid!"); + } else if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 401) { + message = tr("Key is invalid!"); + } else { + message = tr("An error occurred: %1").arg(reply->errorString()); + } + ConfirmationDialog::alert(message, this); + reply->deleteLater(); + }); + } + }); + settingsList->addItem(publicMapboxKeyControl); + + secretMapboxKeyControl = new FrogPilotButtonsControl(tr("Secret Mapbox Key"), tr("Manage your Secret Mapbox Key."), "", {tr("ADD"), tr("TEST")}); + QObject::connect(secretMapboxKeyControl, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + if (id == 0) { + if (mapboxSecretKeySet) { + if (FrogPilotConfirmationDialog::yesorno(tr("Remove your Secret Mapbox Key?"), this)) { + params.remove("MapboxSecretKey"); + + updateButtons(); + } + } else { + int minKeyLength = 80; + QString key = InputDialog::getText(tr("Enter your Secret Mapbox Key"), this, "", false, minKeyLength).trimmed(); + if (!key.isEmpty()) { + if (!key.startsWith("sk.")) { + key = "sk." + key; + } + params.put("MapboxSecretKey", key.toStdString()); + updateButtons(); + } + } + } else { + secretMapboxKeyControl->setValue(tr("Testing...")); + + QString key = QString::fromStdString(params.get("MapboxSecretKey")); + QString url = QString("https://api.mapbox.com/directions/v5/mapbox/driving/-73.989,40.733;-74,40.733?access_token=%1").arg(key); + + QNetworkRequest request(url); + QNetworkReply *reply = networkManager->get(request); + connect(reply, &QNetworkReply::finished, [=]() { + secretMapboxKeyControl->setValue(""); + + QString message; + if (reply->error() == QNetworkReply::NoError) { + message = tr("Key is valid!"); + } else if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 401) { + message = tr("Key is invalid!"); + } else { + message = tr("An error occurred: %1").arg(reply->errorString()); + } + ConfirmationDialog::alert(message, this); + reply->deleteLater(); + }); + } + }); + settingsList->addItem(secretMapboxKeyControl); + + setupButton = new ButtonControl(tr("Mapbox Setup Instructions"), tr("VIEW"), tr("Instructions on how to set up Mapbox for \"Primeless Navigation\"."), this); + QObject::connect(setupButton, &ButtonControl::clicked, [this]() { + openSubPanel(); + + updateStep(); + + primelessLayout->setCurrentIndex(1); + }); + settingsList->addItem(setupButton); + + std::vector filterButtonNames{tr("CANCEL"), tr("Manually Update Speed Limits")}; + updateSpeedLimitsToggle = new FrogPilotButtonControl("SpeedLimitFiller", tr("Speed Limit Filler"), + tr("Automatically collect missing or incorrect speed limits while you drive using speeds limits sourced from your dashboard (if supported), " + "Mapbox, and \"Navigate on openpilot\".

" + "When you're parked and connected to Wi-Fi, FrogPilot will automatically processes this data into a file " + "to be used with the tool located at \"SpeedLimitFiller.frogpilot.com\".

" + "You can download this file from \"The Pond\" in the \"Download Speed Limits\" menu.

" + "Need a step-by-step guide? Visit #speed-limit-filler in the FrogPilot Discord!"), + "", filterButtonNames); + QObject::connect(updateSpeedLimitsToggle, &FrogPilotButtonControl::buttonClicked, [this](int id) { + if (id == 0) { + if (FrogPilotConfirmationDialog::yesorno(tr("Cancel the speed-limit update?"), this)) { + updatingLimits = false; + + updateSpeedLimitsToggle->setEnabledButton(0, false); + updateSpeedLimitsToggle->setValue(tr("Cancelled...")); + + params_memory.remove("UpdateSpeedLimits"); + + QTimer::singleShot(2500, [this]() { + updateSpeedLimitsToggle->clearCheckedButtons(); + updateSpeedLimitsToggle->setEnabledButton(0, true); + updateSpeedLimitsToggle->setValue(""); + updateSpeedLimitsToggle->setVisibleButton(0, false); + updateSpeedLimitsToggle->setVisibleButton(1, true); + + params_memory.remove("UpdateSpeedLimitsStatus"); + }); + } + } else if (id == 1) { + QJsonObject overpassRequests = QJsonDocument::fromJson(QString::fromStdString(params.get("OverpassRequests")).toUtf8()).object(); + + int totalRequests = overpassRequests.value("total_requests").toInt(0); + int maxRequests = overpassRequests.value("max_requests").toInt(10000); + int savedDay = overpassRequests.value("day").toInt(QDate::currentDate().day()); + + int currentDay = QDate::currentDate().day(); + + if (savedDay != currentDay) { + totalRequests = 0; + } + + if (totalRequests >= maxRequests) { + QTime now = QTime::currentTime(); + + int secondsUntilMidnight = (24 * 3600) - (now.hour() * 3600 + now.minute() * 60 + now.second()); + int hours = secondsUntilMidnight / 3600; + int minutes = (secondsUntilMidnight % 3600) / 60; + + ConfirmationDialog::alert(QString(tr("You've hit today's request limit.\n\nIt will reset in %1 hours and %2 minutes.")).arg(hours).arg(minutes), this); + + updateSpeedLimitsToggle->clearCheckedButtons(); + return; + } + + updateSpeedLimitsToggle->setVisibleButton(0, true); + updateSpeedLimitsToggle->setVisibleButton(1, false); + + if (FrogPilotConfirmationDialog::yesorno(tr("This process takes a while. It's recommended to start when you're done driving and connected to stable Wi-Fi. Continue?"), this)) { + updatingLimits = true; + + updateSpeedLimitsToggle->setValue("Calculating..."); + + params_memory.put("UpdateSpeedLimitsStatus", "Calculating..."); + params_memory.putBool("UpdateSpeedLimits", true); + } else { + updateSpeedLimitsToggle->setVisibleButton(0, false); + updateSpeedLimitsToggle->setVisibleButton(1, true); + + updateSpeedLimitsToggle->clearCheckedButtons(); + } + } + }); + updateSpeedLimitsToggle->setVisibleButton(0, false); + settingsList->addItem(updateSpeedLimitsToggle); + + ScrollView *settingsPanel = new ScrollView(settingsList, this); + primelessLayout->addWidget(settingsPanel); + + imageLabel = new QLabel(this); + + ScrollView *instructionsPanel = new ScrollView(imageLabel, this); + primelessLayout->addWidget(instructionsPanel); + + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [this]() { + primelessLayout->setCurrentIndex(0); + + if (forceOpenDescriptions) { + publicMapboxKeyControl->showDescription(); + secretMapboxKeyControl->showDescription(); + setupButton->showDescription(); + updateSpeedLimitsToggle->showDescription(); + } + }); + QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotNavigationPanel::updateState); +} + +void FrogPilotNavigationPanel::showEvent(QShowEvent *event) { + if (forceOpenDescriptions) { + publicMapboxKeyControl->showDescription(); + secretMapboxKeyControl->showDescription(); + setupButton->showDescription(); + updateSpeedLimitsToggle->showDescription(); + } + + UIState &s = *uiState(); + + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + QString ipAddress = fs.wifi->getIp4Address(); + ipLabel->setText(ipAddress.isEmpty() ? tr("Offline...") : QString("%1:8082").arg(ipAddress)); + + updateButtons(); + + updatingLimits = !params_memory.get("UpdateSpeedLimitsStatus").empty() && QString::fromStdString(params_memory.get("UpdateSpeedLimitsStatus")) != "Completed!"; + + bool parked = !s.scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo; + + updateSpeedLimitsToggle->setVisibleButton(0, updatingLimits); + updateSpeedLimitsToggle->setVisibleButton(1, !updatingLimits); + + if (updatingLimits) { + updateSpeedLimitsToggle->setValue(QString::fromStdString(params_memory.get("UpdateSpeedLimitsStatus"))); + } else { + updateSpeedLimitsToggle->setEnabledButton(1, frogpilot_scene.online && util::system_time_valid() && parked); + updateSpeedLimitsToggle->setValue(frogpilot_scene.online ? (parked ? "" : "Not parked") : tr("Offline...")); + updateSpeedLimitsToggle->setVisible(parent->tuningLevel >= parent->frogpilotToggleLevels["SpeedLimitFiller"].toDouble()); + } +} + +void FrogPilotNavigationPanel::hideEvent(QHideEvent *event) { + primelessLayout->setCurrentIndex(0); +} + +void FrogPilotNavigationPanel::mousePressEvent(QMouseEvent *event) { + if (primelessLayout->currentIndex() == 1) { + closeSubPanel(); + + primelessLayout->setCurrentIndex(0); + + if (forceOpenDescriptions) { + publicMapboxKeyControl->showDescription(); + secretMapboxKeyControl->showDescription(); + setupButton->showDescription(); + updateSpeedLimitsToggle->showDescription(); + } + } +} + +void FrogPilotNavigationPanel::updateButtons() { + FrogPilotUIState &fs = *frogpilotUIState(); + FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + mapboxPublicKeySet = QString::fromStdString(params.get("MapboxPublicKey")).startsWith("pk"); + mapboxSecretKeySet = QString::fromStdString(params.get("MapboxSecretKey")).startsWith("sk"); + + publicMapboxKeyControl->setText(0, mapboxPublicKeySet ? tr("REMOVE") : tr("ADD")); + publicMapboxKeyControl->setVisibleButton(1, mapboxPublicKeySet && frogpilot_scene.online); + secretMapboxKeyControl->setText(0, mapboxSecretKeySet ? tr("REMOVE") : tr("ADD")); + secretMapboxKeyControl->setVisibleButton(1, mapboxSecretKeySet && frogpilot_scene.online); +} + +void FrogPilotNavigationPanel::updateState(const UIState &s, const FrogPilotUIState &fs) { + if (!isVisible() || s.sm->frame % (UI_FREQ / 2) != 0) { + return; + } + + const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + updateButtons(); + updateStep(); + + bool parked = !s.scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo; + + if (updatingLimits) { + if (QString::fromStdString(params_memory.get("UpdateSpeedLimitsStatus")) == "Completed!") { + updatingLimits = false; + + updateSpeedLimitsToggle->setValue(tr("Completed!")); + + QTimer::singleShot(2500, [this]() { + updateSpeedLimitsToggle->clearCheckedButtons(); + updateSpeedLimitsToggle->setValue(""); + updateSpeedLimitsToggle->setVisibleButton(0, false); + updateSpeedLimitsToggle->setVisibleButton(1, true); + + params_memory.remove("UpdateSpeedLimitsStatus"); + }); + } else { + updateSpeedLimitsToggle->setValue(QString::fromStdString(params_memory.get("UpdateSpeedLimitsStatus"))); + } + } else { + updateSpeedLimitsToggle->setEnabledButton(1, frogpilot_scene.online && util::system_time_valid() && parked); + updateSpeedLimitsToggle->setValue(frogpilot_scene.online ? (parked ? "" : "Not parked") : tr("Offline...")); + } + + parent->keepScreenOn = primelessLayout->currentIndex() == 1 || updatingLimits; +} + +void FrogPilotNavigationPanel::updateStep() { + QString currentStep; + if (mapboxPublicKeySet) { + currentStep = "../../frogpilot/navigation/navigation_training/public_key_set.png"; + } else { + currentStep = "../../frogpilot/navigation/navigation_training/no_keys_set.png"; + } + + QPixmap pixmap; + pixmap.load(currentStep); + imageLabel->setPixmap(pixmap.scaledToWidth(1500, Qt::SmoothTransformation)); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/navigation_settings.h b/frogpilot/ui/qt/offroad/navigation_settings.h new file mode 100644 index 000000000..39d997184 --- /dev/null +++ b/frogpilot/ui/qt/offroad/navigation_settings.h @@ -0,0 +1,47 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotNavigationPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotNavigationPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +signals: + void closeSubPanel(); + void openSubPanel(); + +protected: + void hideEvent(QHideEvent *event); + void showEvent(QShowEvent *event) override; + +private: + void mousePressEvent(QMouseEvent *event); + void updateButtons(); + void updateState(const UIState &s, const FrogPilotUIState &fs); + void updateStep(); + + bool forceOpenDescriptions; + bool mapboxPublicKeySet; + bool mapboxSecretKeySet; + bool updatingLimits; + + FrogPilotButtonsControl *publicMapboxKeyControl; + FrogPilotButtonsControl *secretMapboxKeyControl; + ButtonControl *setupButton; + + FrogPilotButtonControl *updateSpeedLimitsToggle; + + FrogPilotSettingsWindow *parent; + + LabelControl *ipLabel; + + Params params; + + QLabel *imageLabel; + + QNetworkAccessManager *networkManager; + + QStackedLayout *primelessLayout; +}; diff --git a/frogpilot/ui/qt/offroad/sounds_settings.cc b/frogpilot/ui/qt/offroad/sounds_settings.cc new file mode 100644 index 000000000..703983932 --- /dev/null +++ b/frogpilot/ui/qt/offroad/sounds_settings.cc @@ -0,0 +1,164 @@ +#include "frogpilot/ui/qt/offroad/sounds_settings.h" + +FrogPilotSoundsPanel::FrogPilotSoundsPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + QStackedLayout *soundsLayout = new QStackedLayout(); + addItem(soundsLayout); + + FrogPilotListWidget *soundsList = new FrogPilotListWidget(this); + + ScrollView *soundsPanel = new ScrollView(soundsList, this); + + soundsLayout->addWidget(soundsPanel); + + FrogPilotListWidget *alertVolumeControlList = new FrogPilotListWidget(this); + FrogPilotListWidget *customAlertsList = new FrogPilotListWidget(this); + + ScrollView *alertVolumeControlPanel = new ScrollView(alertVolumeControlList, this); + ScrollView *customAlertsPanel = new ScrollView(customAlertsList, this); + + soundsLayout->addWidget(alertVolumeControlPanel); + soundsLayout->addWidget(customAlertsPanel); + + const std::vector> soundsToggles { + {"AlertVolumeControl", tr("Alert Volume Controller"), tr("Set how loud each type of openpilot alert is to keep routine prompts from becoming distracting."), "../../frogpilot/assets/toggle_icons/icon_mute.png"}, + {"DisengageVolume", tr("Disengage Volume"), tr("Set the volume for alerts when openpilot disengages.

Examples include: \"Cruise Fault: Restart the Car\", \"Parking Brake Engaged\", \"Pedal Pressed\"."), ""}, + {"EngageVolume", tr("Engage Volume"), tr("Set the volume for the chime when openpilot engages, such as after pressing the \"RESUME\" or \"SET\" steering wheel buttons."), ""}, + {"PromptVolume", tr("Prompt Volume"), tr("Set the volume for prompts that need attention.

Examples include: \"Car Detected in Blindspot\", \"Steering Temporarily Unavailable\", \"Turn Exceeds Steering Limit\"."), ""}, + {"PromptDistractedVolume", tr("Prompt Distracted Volume"), tr("Set the volume for prompts when openpilot detects driver distraction or unresponsiveness.

Examples include: \"Pay Attention\", \"Touch Steering Wheel\"."), ""}, + {"RefuseVolume", tr("Refuse Volume"), tr("Set the volume for alerts when openpilot refuses to engage.

Examples include: \"Brake Hold Active\", \"Door Open\", \"Seatbelt Unlatched\"."), ""}, + {"WarningSoftVolume", tr("Warning Soft Volume"), tr("Set the volume for softer warnings about potential risks.

Examples include: \"BRAKE! Risk of Collision\", \"Steering Temporarily Unavailable\"."), ""}, + {"WarningImmediateVolume", tr("Warning Immediate Volume"), tr("Set the volume for the loudest warnings that require urgent attention.

Examples include: \"DISENGAGE IMMEDIATELY — Driver Distracted\", \"DISENGAGE IMMEDIATELY — Driver Unresponsive\"."), ""}, + + {"CustomAlerts", tr("FrogPilot Alerts"), tr("Optional FrogPilot alerts that highlight driving events in a more noticeable way."), "../../frogpilot/assets/toggle_icons/icon_green_light.png"}, + {"GoatScream", tr("Goat Scream"), tr("Play the infamous \"Goat Scream\" when the steering controller reaches its limit. Based on the \"Turn Exceeds Steering Limit\" event."), ""}, + {"GreenLightAlert", tr("Green Light Alert"), tr("Play an alert when the model predicts a red light has turned green.

Disclaimer: openpilot does not explicitly detect traffic lights. This alert is based on end-to-end model predictions from camera input and may trigger even when the light has not changed."), ""}, + {"LeadDepartingAlert", tr("Lead Departing Alert"), tr("Play an alert when the lead vehicle departs from a stop."), ""}, + {"LoudBlindspotAlert", tr("Loud \"Car Detected in Blindspot\" Alert"), tr("Play a louder alert if a vehicle is in the blind spot when attempting to change lanes. Based on the \"Car Detected in Blindspot\" event."), ""}, + {"SpeedLimitChangedAlert", tr("Speed Limit Changed Alert"), tr("Play an alert when the posted speed limit changes."), ""} + }; + + for (const auto &[param, title, desc, icon] : soundsToggles) { + AbstractControl *soundsToggle; + + if (param == "AlertVolumeControl") { + FrogPilotManageControl *alertVolumeControlToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(alertVolumeControlToggle, &FrogPilotManageControl::manageButtonClicked, [soundsLayout, alertVolumeControlPanel]() { + soundsLayout->setCurrentWidget(alertVolumeControlPanel); + }); + soundsToggle = alertVolumeControlToggle; + } else if (alertVolumeControlKeys.contains(param)) { + std::map volumeLabels; + for (int i = 0; i <= 101; ++i) { + volumeLabels[i] = i == 0 ? tr("Muted") : i == 101 ? tr("Auto") : QString::number(i) + "%"; + } + std::vector alertButton{tr("Test")}; + if (param == "WarningImmediateVolume" || param == "WarningSoftVolume") { + soundsToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 25, 101, QString(), volumeLabels, 1, true, {}, alertButton, false, false); + } else { + soundsToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0, 101, QString(), volumeLabels, 1, true, {}, alertButton, false, false); + } + + } else if (param == "CustomAlerts") { + FrogPilotManageControl *customAlertsToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(customAlertsToggle, &FrogPilotManageControl::manageButtonClicked, [soundsLayout, customAlertsPanel]() { + soundsLayout->setCurrentWidget(customAlertsPanel); + }); + soundsToggle = customAlertsToggle; + + } else { + soundsToggle = new ParamControl(param, title, desc, icon); + } + + toggles[param] = soundsToggle; + + if (alertVolumeControlKeys.contains(param)) { + alertVolumeControlList->addItem(soundsToggle); + } else if (customAlertsKeys.contains(param)) { + customAlertsList->addItem(soundsToggle); + } else { + soundsList->addItem(soundsToggle); + + parentKeys.insert(param); + } + + if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast(soundsToggle)) { + QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { + emit openSubPanel(); + openDescriptions(forceOpenDescriptions, toggles); + }); + } + + QObject::connect(soundsToggle, &AbstractControl::hideDescriptionEvent, [this]() { + update(); + }); + QObject::connect(soundsToggle, &AbstractControl::showDescriptionEvent, [this]() { + update(); + }); + } + + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [soundsLayout, soundsPanel, this] { + openDescriptions(forceOpenDescriptions, toggles); + soundsLayout->setCurrentWidget(soundsPanel); + }); + QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotSoundsPanel::updateState); + + for (auto &[key, toggle] : toggles) { + if (alertVolumeControlKeys.contains(key)) { + toggle->setVisible(true); + } + } + + updateToggles(); +} + +void FrogPilotSoundsPanel::showEvent(QShowEvent *event) { + updateToggles(); +} + +void FrogPilotSoundsPanel::updateState(const UIState &s) { + if (!isVisible()) { + return; + } + + started = s.scene.started; +} + +void FrogPilotSoundsPanel::updateToggles() { + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + toggle->setVisible(false); + } + } + + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + continue; + } + + bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble(); + + if (key == "LoudBlindspotAlert") { + setVisible &= parent->hasBSM; + } + + else if (key == "SpeedLimitChangedAlert") { + setVisible &= params.getBool("ShowSpeedLimits") || (parent->hasOpenpilotLongitudinal && params.getBool("SpeedLimitController")); + } + + toggle->setVisible(setVisible); + + if (setVisible) { + if (alertVolumeControlKeys.contains(key)) { + toggles["AlertVolumeControl"]->setVisible(true); + } else if (customAlertsKeys.contains(key)) { + toggles["CustomAlerts"]->setVisible(true); + } + } + } + + openDescriptions(forceOpenDescriptions, toggles); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/sounds_settings.h b/frogpilot/ui/qt/offroad/sounds_settings.h new file mode 100644 index 000000000..6e9d3f7fc --- /dev/null +++ b/frogpilot/ui/qt/offroad/sounds_settings.h @@ -0,0 +1,34 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotSoundsPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotSoundsPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +signals: + void openSubPanel(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void updateState(const UIState &s); + void updateToggles(); + + bool forceOpenDescriptions; + bool started; + + std::map toggles; + + QSet alertVolumeControlKeys {"DisengageVolume", "EngageVolume", "PromptDistractedVolume", "PromptVolume", "RefuseVolume", "WarningImmediateVolume", "WarningSoftVolume"}; + QSet customAlertsKeys {"GoatScream", "GreenLightAlert", "LeadDepartingAlert", "LoudBlindspotAlert", "SpeedLimitChangedAlert"}; + + QSet parentKeys; + + FrogPilotSettingsWindow *parent; + + Params params; +}; diff --git a/frogpilot/ui/qt/offroad/theme_settings.cc b/frogpilot/ui/qt/offroad/theme_settings.cc new file mode 100644 index 000000000..5a9d90a9b --- /dev/null +++ b/frogpilot/ui/qt/offroad/theme_settings.cc @@ -0,0 +1,869 @@ +#include "frogpilot/ui/qt/offroad/theme_settings.h" + +bool isUserCreatedTheme(const QString &themeName) { + return themeName.endsWith("-user_created"); +} + +void updateAssetParam(const QString &assetParam, Params ¶ms, const QString &value, bool add) { + QStringList assets = QString::fromStdString(params.get(assetParam.toStdString())).split(",", QString::SkipEmptyParts); + if (add) { + if (!assets.contains(value)) { + assets.append(value); + } + } else { + assets.removeAll(value); + } + assets.sort(); + + params.put(assetParam.toStdString(), assets.join(",").toStdString()); +} + +void deleteThemeAsset(QDir &directory, const QString &subFolder, const QString &assetParam, const QString &themeToDelete, Params ¶ms) { + bool useFiles = subFolder.isEmpty(); + + QString baseName = themeToDelete.toLower(); + baseName.replace("(", "-").replace(")", "").replace(" ", "-"); + baseName.remove(QRegularExpression("[^a-z0-9\\-]")); + while (baseName.endsWith("-")) { + baseName.chop(1); + } + + QString baseUnderscore = baseName; + baseUnderscore.replace("-", "_"); + + QStringList candidateNames = { + baseName, + baseName + "-user-created", + baseUnderscore, + baseUnderscore + "-user_created" + }; + + if (useFiles) { + QStringList files = directory.entryList(QDir::Files); + for (QString &file : files) { + QString normalizedFile = QFileInfo(file).baseName().toLower(); + normalizedFile.replace("_", "-"); + normalizedFile.remove(QRegularExpression("[^a-z0-9\\-~]")); + + if (candidateNames.contains(normalizedFile)) { + QFile::remove(directory.filePath(file)); + break; + } + } + } else { + for (QString &candidate : candidateNames) { + QString fullSubPath = QDir(candidate).filePath(subFolder); + QDir targetDir(directory.filePath(fullSubPath)); + + if (targetDir.exists()) { + targetDir.removeRecursively(); + break; + } + } + } + + updateAssetParam(assetParam, params, themeToDelete, true); +} + +void downloadThemeAsset(const QString &input, const std::string ¶mKey, const QString &assetParam, Params ¶ms, Params ¶ms_memory) { + QString output = input; + int tilde = output.indexOf("~"); + if (tilde >= 0) { + output = output.left(tilde).toLower() + "~" + output.mid(tilde + 1); + } else { + output = output.toLower(); + } + output.remove("(").remove(")"); + output.replace(" ", input.contains("(") ? "-" : "_"); + + params_memory.put(paramKey, output.toStdString()); +} + +QStringList getHolidayThemes() { + return QStringList() + << "New Year's" + << "Valentine's Day" + << "St. Patrick's Day" + << "World Frog Day" + << "April Fools" + << "Easter" + << "May the Fourth" + << "Cinco de Mayo" + << "Stitch Day" + << "Fourth of July" + << "Halloween" + << "Thanksgiving" + << "Christmas"; +} + +QStringList getThemeList(const bool &randomThemes, const QDir &themePacksDirectory, const QString &subFolder, const QString &assetParam, Params ¶ms) { + bool useFiles = subFolder.isEmpty(); + + QString currentAsset = randomThemes ? "" : QString::fromStdString(params.get(assetParam.toStdString())); + + QStringList themeList; + for (const QFileInfo &entry : themePacksDirectory.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) { + if (entry.baseName() == currentAsset) { + continue; + } + + if (useFiles && entry.isDir()) { + continue; + } + + if (!useFiles) { + QString targetPath = QDir(entry.filePath()).filePath(subFolder); + if (!QFileInfo(targetPath).exists()) { + continue; + } + } + + QString baseName = entry.baseName(); + bool userCreated = isUserCreatedTheme(baseName); + if (userCreated) { + baseName = baseName.replace("-user_created", ""); + } + + int tildeIdx = baseName.indexOf("~"); + QString creator; + if (tildeIdx >= 0) { + creator = baseName.mid(tildeIdx + 1); + baseName = baseName.left(tildeIdx); + } + + QStringList parts = baseName.split(baseName.contains("-") ? "-" : "_", QString::SkipEmptyParts); + for (QString &part : parts) { + part[0] = part[0].toUpper(); + } + + QString displayName; + if (userCreated) { + displayName = parts.join(" "); + } else { + displayName = (parts.size() <= 1 || useFiles) ? parts.join(" ") : QString("%1 (%2)").arg(parts[0], parts.mid(1).join(" ")); + } + + if (userCreated) { + displayName += " 🌟"; + } + if (!creator.isEmpty()) { + displayName += " - by: " + creator; + } + + themeList.append(displayName); + } + + return themeList; +} + +QString getThemeName(const std::string ¶mKey, Params ¶ms) { + QString value = QString::fromStdString(params.get(paramKey)); + + QString baseName = value; + + int tildeIdx = baseName.indexOf("~"); + QString creator; + if (tildeIdx >= 0) { + creator = baseName.mid(tildeIdx + 1); + baseName = baseName.left(tildeIdx); + } + + QStringList parts = baseName.split(baseName.contains("-") ? "-" : "_", QString::SkipEmptyParts); + for (QString &part : parts) { + part[0] = part[0].toUpper(); + } + + QString displayName; + if (baseName.contains("-") && parts.size() > 1) { + displayName = QString("%1 (%2)").arg(parts[0], parts.mid(1).join(" ")); + } else { + displayName = parts.join(" "); + } + + if (isUserCreatedTheme(value)) { + displayName = displayName.split(" (")[0] + " 🌟"; + } + if (!creator.isEmpty()) { + displayName += " - by: " + creator; + } + + return displayName; +} + +QString storeThemeName(const QString &input, const std::string ¶mKey, Params ¶ms) { + QString output = input.toLower().remove("(").remove(")").remove("'").remove("."); + output.replace(" ", input.contains("(") ? "-" : "_"); + output.replace("_🌟", "-user_created"); + output = output.trimmed(); + + params.put(paramKey, output.toStdString()); + + return getThemeName(paramKey, params); +} + +FrogPilotThemesPanel::FrogPilotThemesPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + QStackedLayout *themesLayout = new QStackedLayout(); + addItem(themesLayout); + + FrogPilotListWidget *themesList = new FrogPilotListWidget(this); + + ScrollView *themesPanel = new ScrollView(themesList, this); + + themesLayout->addWidget(themesPanel); + + FrogPilotListWidget *customThemesList = new FrogPilotListWidget(this); + + ScrollView *customThemesPanel = new ScrollView(customThemesList, this); + + themesLayout->addWidget(customThemesPanel); + + const std::vector> themeToggles { + {"CustomThemes", tr("Custom Themes"), tr("The overall look and feel of openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), "../../frogpilot/assets/toggle_icons/icon_frog.png"}, + {"ColorScheme", tr("Color Scheme"), tr("The color scheme used throughout openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""}, + {"DistanceIconPack", tr("Distance Button"), tr("The distance button icons shown on the driving screen. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""}, + {"IconPack", tr("Icon Pack"), tr("The icon style used across openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""}, + {"SignalAnimation", tr("Turn Signal"), tr("Themed turn-signal animations. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""}, + {"SoundPack", tr("Sound Pack"), tr("The sound pack used by openpilot. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""}, + {"WheelIcon", tr("Steering Wheel"), tr("The steering-wheel icon shown at the top-right of the driving screen. Use the \"Theme Maker\" in \"The Pond\" to create and share your own themes!"), ""}, + {"DownloadStatusLabel", tr("Download Status"), "", ""}, + + {"HolidayThemes", tr("Holiday Themes"), tr("Themes based on U.S. holidays. Minor holidays last one day; major holidays (Christmas, Easter, Halloween) run for a full week."), "../../frogpilot/assets/toggle_icons/icon_calendar.png"}, + {"RainbowPath", tr("Rainbow Path"), tr("Color the driving path like a Mario Kart–style \"Rainbow Road\"."), "../../frogpilot/assets/toggle_icons/icon_rainbow.png"}, + {"RandomEvents", tr("Random Events"), tr("Occasional on-screen effects triggered by driving conditions. These are purely a visual and don't impact how openpilot drives!"), "../../frogpilot/assets/toggle_icons/icon_random.png"}, + {"RandomThemes", tr("Random Themes"), tr("Pick a random theme between each drive from the themes you have downloaded. Great for variety without changing settings while driving."), "../../frogpilot/assets/toggle_icons/icon_random_themes.png"}, + {"StartupAlert", tr("Startup Alert"), tr("Customize the \"Startup Alert\" message shown at the start of each drive."), "../../frogpilot/assets/toggle_icons/icon_message.png"} + }; + + for (const auto &[param, title, desc, icon] : themeToggles) { + AbstractControl *themeToggle; + + if (param == "CustomThemes") { + FrogPilotManageControl *personalizeOpenpilotToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(personalizeOpenpilotToggle, &FrogPilotManageControl::manageButtonClicked, [customThemesPanel, themesLayout]() { + themesLayout->setCurrentWidget(customThemesPanel); + }); + themeToggle = personalizeOpenpilotToggle; + } else if (param == "ColorScheme") { + manageColorSchemeButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}); + QObject::connect(manageColorSchemeButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + QStringList colorSchemes = getThemeList(randomThemes, QDir(themePacksDirectory.path()), "colors", "ColorScheme", params); + + if (id == 0) { + QString colorSchemeToDelete = MultiOptionDialog::getSelection(tr("Select a color scheme to delete"), colorSchemes, "", this); + if (!colorSchemeToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" color scheme?").arg(colorSchemeToDelete), tr("Delete"), this)) { + colorsDownloaded = false; + + deleteThemeAsset(themePacksDirectory, "colors", "DownloadableColors", colorSchemeToDelete, params); + } + } else if (id == 1) { + if (colorDownloading) { + cancellingDownload = true; + + params_memory.putBool("CancelThemeDownload", true); + + QTimer::singleShot(2500, [this]() { + cancellingDownload = false; + colorDownloading = false; + themeDownloading = false; + + params_memory.putBool("CancelThemeDownload", false); + }); + } else { + QStringList downloadableColorSchemes = QString::fromStdString(params.get("DownloadableColors")).split(","); + colorSchemeToDownload = MultiOptionDialog::getSelection(tr("Select a color scheme to download"), downloadableColorSchemes, "", this); + if (!colorSchemeToDownload.isEmpty()) { + colorDownloading = true; + themeDownloading = true; + + params_memory.put("ThemeDownloadProgress", "Downloading..."); + + downloadThemeAsset(colorSchemeToDownload, "ColorToDownload", "DownloadableColors", params, params_memory); + + downloadStatusLabel->setText(tr("Downloading...")); + } + } + } else if (id == 2) { + colorSchemes.append("Stock"); + colorSchemes.append(getHolidayThemes()); + colorSchemes.sort(); + + QString colorSchemeToSelect = MultiOptionDialog::getSelection(tr("Select a color scheme"), colorSchemes, getThemeName("ColorScheme", params), this); + if (!colorSchemeToSelect.isEmpty()) { + manageColorSchemeButton->setValue(storeThemeName(colorSchemeToSelect, "ColorScheme", params)); + } + } + }); + manageColorSchemeButton->setValue(getThemeName(param.toStdString(), params)); + themeToggle = manageColorSchemeButton; + } else if (param == "DistanceIconPack") { + manageDistanceIconPackButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}); + QObject::connect(manageDistanceIconPackButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + QStringList distanceIconPacks = getThemeList(randomThemes, QDir(themePacksDirectory.path()), "distance_icons", "DistanceIconPack", params); + + if (id == 0) { + QString distanceIconPackToDelete = MultiOptionDialog::getSelection(tr("Select a distance icon pack to delete"), distanceIconPacks, "", this); + if (!distanceIconPackToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" distance icon pack?").arg(distanceIconPackToDelete), tr("Delete"), this)) { + distanceIconsDownloaded = false; + + deleteThemeAsset(themePacksDirectory, "distance_icons", "DownloadableDistanceIcons", distanceIconPackToDelete, params); + } + } else if (id == 1) { + if (distanceIconDownloading) { + cancellingDownload = true; + + params_memory.putBool("CancelThemeDownload", true); + + QTimer::singleShot(2500, [this]() { + cancellingDownload = false; + distanceIconDownloading = false; + themeDownloading = false; + + params_memory.putBool("CancelThemeDownload", false); + }); + } else { + QStringList downloadableDistanceIconPacks = QString::fromStdString(params.get("DownloadableDistanceIcons")).split(","); + distanceIconPackToDownload = MultiOptionDialog::getSelection(tr("Select a distance icon pack to download"), downloadableDistanceIconPacks, "", this); + if (!distanceIconPackToDownload.isEmpty()) { + distanceIconDownloading = true; + themeDownloading = true; + + params_memory.put("ThemeDownloadProgress", "Downloading..."); + + downloadThemeAsset(distanceIconPackToDownload, "DistanceIconToDownload", "DownloadableDistanceIcons", params, params_memory); + + downloadStatusLabel->setText(tr("Downloading...")); + } + } + } else if (id == 2) { + distanceIconPacks.append("Stock"); + distanceIconPacks.append(getHolidayThemes()); + distanceIconPacks.sort(); + + QString distanceIconPackToSelect = MultiOptionDialog::getSelection(tr("Select a distance icon pack"), distanceIconPacks, getThemeName("DistanceIconPack", params), this); + if (!distanceIconPackToSelect.isEmpty()) { + manageDistanceIconPackButton->setValue(storeThemeName(distanceIconPackToSelect, "DistanceIconPack", params)); + } + } + }); + manageDistanceIconPackButton->setValue(getThemeName(param.toStdString(), params)); + themeToggle = manageDistanceIconPackButton; + } else if (param == "IconPack") { + manageIconPackButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}); + QObject::connect(manageIconPackButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + QStringList iconPacks = getThemeList(randomThemes, QDir(themePacksDirectory.path()), "icons", "IconPack", params); + + if (id == 0) { + QString iconPackToDelete = MultiOptionDialog::getSelection(tr("Select an icon pack to delete"), iconPacks, "", this); + if (!iconPackToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" icon pack?").arg(iconPackToDelete), tr("Delete"), this)) { + iconsDownloaded = false; + + deleteThemeAsset(themePacksDirectory, "icons", "DownloadableIcons", iconPackToDelete, params); + } + } else if (id == 1) { + if (iconDownloading) { + cancellingDownload = true; + + params_memory.putBool("CancelThemeDownload", true); + + QTimer::singleShot(2500, [this]() { + cancellingDownload = false; + iconDownloading = false; + themeDownloading = false; + + params_memory.putBool("CancelThemeDownload", false); + }); + } else { + QStringList downloadableIconPacks = QString::fromStdString(params.get("DownloadableIcons")).split(","); + iconPackToDownload = MultiOptionDialog::getSelection(tr("Select an icon pack to download"), downloadableIconPacks, "", this); + if (!iconPackToDownload.isEmpty()) { + iconDownloading = true; + themeDownloading = true; + + params_memory.put("ThemeDownloadProgress", "Downloading..."); + + downloadThemeAsset(iconPackToDownload, "IconToDownload", "DownloadableIcons", params, params_memory); + + downloadStatusLabel->setText(tr("Downloading...")); + } + } + } else if (id == 2) { + iconPacks.append("Stock"); + iconPacks.append(getHolidayThemes()); + iconPacks.sort(); + + QString iconPackToSelect = MultiOptionDialog::getSelection(tr("Select an icon pack"), iconPacks, getThemeName("IconPack", params), this); + if (!iconPackToSelect.isEmpty()) { + manageIconPackButton->setValue(storeThemeName(iconPackToSelect, "IconPack", params)); + } + } + }); + manageIconPackButton->setValue(getThemeName(param.toStdString(), params)); + themeToggle = manageIconPackButton; + } else if (param == "SignalAnimation") { + manageSignalAnimationButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}); + QObject::connect(manageSignalAnimationButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + QStringList signalAnimations = getThemeList(randomThemes, QDir(themePacksDirectory.path()), "signals", "SignalAnimation", params); + + if (id == 0) { + QString signalAnimationToDelete = MultiOptionDialog::getSelection(tr("Select a signal animation to delete"), signalAnimations, "", this); + if (!signalAnimationToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" signal animation?").arg(signalAnimationToDelete), tr("Delete"), this)) { + signalsDownloaded = false; + + deleteThemeAsset(themePacksDirectory, "signals", "DownloadableSignals", signalAnimationToDelete, params); + } + } else if (id == 1) { + if (signalDownloading) { + cancellingDownload = true; + + params_memory.putBool("CancelThemeDownload", true); + + QTimer::singleShot(2500, [this]() { + cancellingDownload = false; + signalDownloading = false; + themeDownloading = false; + + params_memory.putBool("CancelThemeDownload", false); + }); + } else { + QStringList downloadableSignalAnimations = QString::fromStdString(params.get("DownloadableSignals")).split(","); + signalAnimationToDownload = MultiOptionDialog::getSelection(tr("Select a signal animation to download"), downloadableSignalAnimations, "", this); + if (!signalAnimationToDownload.isEmpty()) { + signalDownloading = true; + themeDownloading = true; + + params_memory.put("ThemeDownloadProgress", "Downloading..."); + + downloadThemeAsset(signalAnimationToDownload, "SignalToDownload", "DownloadableSignals", params, params_memory); + + downloadStatusLabel->setText(tr("Downloading...")); + } + } + } else if (id == 2) { + signalAnimations.append("None"); + signalAnimations.append(getHolidayThemes()); + signalAnimations.sort(); + + QString signalAnimationToSelect = MultiOptionDialog::getSelection(tr("Select a signal animation"), signalAnimations, getThemeName("SignalAnimation", params), this); + if (!signalAnimationToSelect.isEmpty()) { + manageSignalAnimationButton->setValue(storeThemeName(signalAnimationToSelect, "SignalAnimation", params)); + } + } + }); + manageSignalAnimationButton->setValue(getThemeName(param.toStdString(), params)); + themeToggle = manageSignalAnimationButton; + } else if (param == "SoundPack") { + manageSoundPackButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}); + QObject::connect(manageSoundPackButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + QStringList soundPacks = getThemeList(randomThemes, QDir(themePacksDirectory.path()), "sounds", "SoundPack", params); + + if (id == 0) { + QString soundPackToDelete = MultiOptionDialog::getSelection(tr("Select a sound pack to delete"), soundPacks, "", this); + if (!soundPackToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" sound pack?").arg(soundPackToDelete), tr("Delete"), this)) { + soundsDownloaded = false; + + deleteThemeAsset(themePacksDirectory, "sounds", "DownloadableSounds", soundPackToDelete, params); + } + } else if (id == 1) { + if (soundDownloading) { + cancellingDownload = true; + + params_memory.putBool("CancelThemeDownload", true); + + QTimer::singleShot(2500, [this]() { + cancellingDownload = false; + soundDownloading = false; + themeDownloading = false; + + params_memory.putBool("CancelThemeDownload", false); + }); + } else { + QStringList downloadableSoundPacks = QString::fromStdString(params.get("DownloadableSounds")).split(","); + soundPackToDownload = MultiOptionDialog::getSelection(tr("Select a sound pack to download"), downloadableSoundPacks, "", this); + if (!soundPackToDownload.isEmpty()) { + soundDownloading = true; + themeDownloading = true; + + params_memory.put("ThemeDownloadProgress", "Downloading..."); + + downloadThemeAsset(soundPackToDownload, "SoundToDownload", "DownloadableSounds", params, params_memory); + + downloadStatusLabel->setText(tr("Downloading...")); + } + } + } else if (id == 2) { + soundPacks.append("Stock"); + soundPacks.append(getHolidayThemes()); + soundPacks.sort(); + + QString soundPackToSelect = MultiOptionDialog::getSelection(tr("Select a sound pack"), soundPacks, getThemeName("SoundPack", params), this); + if (!soundPackToSelect.isEmpty()) { + manageSoundPackButton->setValue(storeThemeName(soundPackToSelect, "SoundPack", params)); + } + } + }); + manageSoundPackButton->setValue(getThemeName(param.toStdString(), params)); + themeToggle = manageSoundPackButton; + } else if (param == "WheelIcon") { + manageWheelIconsButton = new FrogPilotButtonsControl(title, desc, icon, {tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}); + QObject::connect(manageWheelIconsButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + QStringList wheelIcons = getThemeList(randomThemes, QDir(wheelsDirectory.path()), "", "WheelIcon", params); + + if (id == 0) { + QString wheelIconToDelete = MultiOptionDialog::getSelection(tr("Select a steering wheel to delete"), wheelIcons, "", this); + if (!wheelIconToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Delete the \"%1\" steering wheel?").arg(wheelIconToDelete), tr("Delete"), this)) { + wheelsDownloaded = false; + + deleteThemeAsset(wheelsDirectory, "", "DownloadableWheels", wheelIconToDelete, params); + } + } else if (id == 1) { + if (wheelDownloading) { + cancellingDownload = true; + + params_memory.putBool("CancelThemeDownload", true); + + QTimer::singleShot(2500, [this]() { + cancellingDownload = false; + wheelDownloading = false; + themeDownloading = false; + + params_memory.putBool("CancelThemeDownload", false); + }); + } else { + QStringList downloadableWheels = QString::fromStdString(params.get("DownloadableWheels")).split(","); + wheelToDownload = MultiOptionDialog::getSelection(tr("Select a steering wheel to download"), downloadableWheels, "", this); + if (!wheelToDownload.isEmpty()) { + wheelDownloading = true; + themeDownloading = true; + + params_memory.put("ThemeDownloadProgress", "Downloading..."); + + downloadThemeAsset(wheelToDownload, "WheelToDownload", "DownloadableWheels", params, params_memory); + + downloadStatusLabel->setText(tr("Downloading...")); + } + } + } else if (id == 2) { + wheelIcons.append("None"); + wheelIcons.append("Stock"); + wheelIcons.append(getHolidayThemes()); + wheelIcons.sort(); + + QString steeringWheelToSelect = MultiOptionDialog::getSelection(tr("Select a steering wheel"), wheelIcons, getThemeName("WheelIcon", params), this); + if (!steeringWheelToSelect.isEmpty()) { + manageWheelIconsButton->setValue(storeThemeName(steeringWheelToSelect, "WheelIcon", params)); + } + } + }); + manageWheelIconsButton->setValue(getThemeName(param.toStdString(), params)); + themeToggle = manageWheelIconsButton; + } else if (param == "DownloadStatusLabel") { + downloadStatusLabel = new LabelControl(title, tr("Idle")); + themeToggle = downloadStatusLabel; + + } else if (param == "RandomThemes") { + std::vector randomThemesToggles{"RandomThemesHolidays"}; + std::vector randomThemesToggleNames{tr("Include Holiday Themes")}; + themeToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, randomThemesToggles, randomThemesToggleNames); + + } else if (param == "StartupAlert") { + FrogPilotButtonsControl *startupAlertButton = new FrogPilotButtonsControl(title, desc, icon, {tr("STOCK"), tr("FROGPILOT"), tr("CUSTOM"), tr("CLEAR")}, true); + + QString currentTop = QString::fromStdString(params.get("StartupMessageTop")); + QString currentBottom = QString::fromStdString(params.get("StartupMessageBottom")); + + QString stockTop = "Be ready to take over at any time"; + QString stockBottom = "Always keep hands on wheel and eyes on road"; + + QString frogpilotTop = "Hop in and buckle up!"; + QString frogpilotBottom = "Human-tested, frog-approved 🐸"; + + if (currentTop == stockTop && currentBottom == stockBottom) { + startupAlertButton->setCheckedButton(0); + } else if (currentTop == frogpilotTop && currentBottom == frogpilotBottom) { + startupAlertButton->setCheckedButton(1); + } else if (!currentTop.isEmpty() || !currentBottom.isEmpty()) { + startupAlertButton->setCheckedButton(2); + } + + QObject::connect(startupAlertButton, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + int maxLengthTop = 35; + int maxLengthBottom = 45; + + if (id == 0) { + params.put("StartupMessageTop", stockTop.toStdString()); + params.put("StartupMessageBottom", stockBottom.toStdString()); + } else if (id == 1) { + params.put("StartupMessageTop", frogpilotTop.toStdString()); + params.put("StartupMessageBottom", frogpilotBottom.toStdString()); + } else if (id == 2) { + QString currentTop = QString::fromStdString(params.get("StartupMessageTop")); + QString newTop = InputDialog::getText(tr("Enter the text for the top half"), this, tr("Characters: 0/%1").arg(maxLengthTop), false, 1, currentTop, maxLengthTop).trimmed(); + if (!newTop.isEmpty()) { + params.put("StartupMessageTop", newTop.toStdString()); + + QString currentBottom = QString::fromStdString(params.get("StartupMessageBottom")); + QString newBottom = InputDialog::getText(tr("Enter the text for the bottom half"), this, tr("Characters: 0/%1").arg(maxLengthBottom), false, 1, currentBottom, maxLengthBottom).trimmed(); + if (!newBottom.isEmpty()) { + params.put("StartupMessageBottom", newBottom.toStdString()); + } + } + } else if (id == 3) { + if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely reset your startup message?"), this)) { + params.remove("StartupMessageTop"); + params.remove("StartupMessageBottom"); + + startupAlertButton->clearCheckedButtons(); + } + } + }); + themeToggle = startupAlertButton; + + } else { + themeToggle = new ParamControl(param, title, desc, icon); + } + + toggles[param] = themeToggle; + + if (customThemeKeys.contains(param)) { + customThemesList->addItem(themeToggle); + } else { + themesList->addItem(themeToggle); + + if (param == "CustomThemes") { + parentKeys.insert(param); + } + } + + if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast(themeToggle)) { + QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { + emit openSubPanel(); + openDescriptions(forceOpenDescriptions, toggles); + }); + } + + QObject::connect(themeToggle, &AbstractControl::hideDescriptionEvent, [this]() { + update(); + }); + QObject::connect(themeToggle, &AbstractControl::showDescriptionEvent, [this]() { + update(); + }); + } + + openDescriptions(forceOpenDescriptions, toggles); + + QObject::connect(static_cast(toggles["CustomThemes"]), &ToggleControl::toggleFlipped, this, &FrogPilotThemesPanel::updateToggles); + QObject::connect(static_cast(toggles["RandomThemes"]), &ToggleControl::toggleFlipped, [this](bool state) { + if (state) { + ConfirmationDialog::alert(tr("\"Random Themes\" only works with downloaded themes, so make sure you download the themes you want it to use!"), this); + + manageColorSchemeButton->setValue(""); + manageColorSchemeButton->setVisibleButton(2, false); + + manageDistanceIconPackButton->setValue(""); + manageDistanceIconPackButton->setVisibleButton(2, false); + + manageIconPackButton->setValue(""); + manageIconPackButton->setVisibleButton(2, false); + + manageSignalAnimationButton->setValue(""); + manageSignalAnimationButton->setVisibleButton(2, false); + + manageSoundPackButton->setValue(""); + manageSoundPackButton->setVisibleButton(2, false); + + manageWheelIconsButton->setValue(""); + manageWheelIconsButton->setVisibleButton(2, false); + } else { + manageColorSchemeButton->setValue(getThemeName("ColorScheme", params)); + manageColorSchemeButton->setVisibleButton(2, true); + + manageDistanceIconPackButton->setValue(getThemeName("DistanceIconPack", params)); + manageDistanceIconPackButton->setVisibleButton(2, true); + + manageIconPackButton->setValue(getThemeName("IconPack", params)); + manageIconPackButton->setVisibleButton(2, true); + + manageSignalAnimationButton->setValue(getThemeName("SignalAnimation", params)); + manageSignalAnimationButton->setVisibleButton(2, true); + + manageSoundPackButton->setValue(getThemeName("SoundPack", params)); + manageSoundPackButton->setVisibleButton(2, true); + + manageWheelIconsButton->setValue(getThemeName("WheelIcon", params)); + manageWheelIconsButton->setVisibleButton(2, true); + } + + randomThemes = state; + }); + + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [themesLayout, themesPanel, this] { + openDescriptions(forceOpenDescriptions, toggles); + themesLayout->setCurrentWidget(themesPanel); + }); + QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotThemesPanel::updateState); +} + +void FrogPilotThemesPanel::showEvent(QShowEvent *event) { + colorsDownloaded = params.get("DownloadableColors").empty(); + distanceIconsDownloaded = params.get("DownloadableDistanceIcons").empty(); + iconsDownloaded = params.get("DownloadableIcons").empty(); + signalsDownloaded = params.get("DownloadableSignals").empty(); + soundsDownloaded = params.get("DownloadableSounds").empty(); + wheelsDownloaded = params.get("DownloadableWheels").empty(); + + if (params.getBool("RandomThemes")) { + manageColorSchemeButton->setValue(""); + manageColorSchemeButton->setVisibleButton(2, false); + + manageDistanceIconPackButton->setValue(""); + manageDistanceIconPackButton->setVisibleButton(2, false); + + manageIconPackButton->setValue(""); + manageIconPackButton->setVisibleButton(2, false); + + manageSignalAnimationButton->setValue(""); + manageSignalAnimationButton->setVisibleButton(2, false); + + manageSoundPackButton->setValue(""); + manageSoundPackButton->setVisibleButton(2, false); + + manageWheelIconsButton->setValue(""); + manageWheelIconsButton->setVisibleButton(2, false); + + randomThemes = true; + } + + updateToggles(); +} + +void FrogPilotThemesPanel::updateState(const UIState &s, const FrogPilotUIState &fs) { + if (!isVisible() || finalizingDownload) { + return; + } + + const FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + if (themeDownloading) { + QString progress = QString::fromStdString(params_memory.get("ThemeDownloadProgress")); + bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|failed|offline", QRegularExpression::CaseInsensitiveOption)); + + if (progress != "Downloading...") { + static const QMap progressTranslations = { + {"Download cancelled...", tr("Download cancelled...")}, + {"Download failed...", tr("Download failed...")}, + {"Downloaded!", tr("Downloaded!")}, + {"GitHub and GitLab are offline...", tr("GitHub and GitLab are offline...")}, + {"Repository unavailable", tr("Repository unavailable")}, + {"Unpacking theme...", tr("Unpacking theme...")}, + {"Verifying authenticity...", tr("Verifying authenticity...")} + }; + + if (progressTranslations.contains(progress)) { + downloadStatusLabel->setText(progressTranslations[progress]); + } else if (progress.endsWith("%")) { + downloadStatusLabel->setText(progress); + } else { + downloadStatusLabel->setText(tr("Idle")); + } + } + + if (progress == "Downloaded!" || downloadFailed) { + finalizingDownload = true; + + QTimer::singleShot(2500, [this]() { + colorDownloading = false; + distanceIconDownloading = false; + finalizingDownload = false; + iconDownloading = false; + signalDownloading = false; + soundDownloading = false; + themeDownloading = false; + wheelDownloading = false; + + colorsDownloaded = params.get("DownloadableColors").empty(); + distanceIconsDownloaded = params.get("DownloadableDistanceIcons").empty(); + iconsDownloaded = params.get("DownloadableIcons").empty(); + signalsDownloaded = params.get("DownloadableSignals").empty(); + soundsDownloaded = params.get("DownloadableSounds").empty(); + wheelsDownloaded = params.get("DownloadableWheels").empty(); + + params_memory.remove("CancelThemeDownload"); + params_memory.remove("ThemeDownloadProgress"); + + downloadStatusLabel->setText(tr("Idle")); + }); + } + } + + bool parked = !s.scene.started || frogpilot_scene.parked || parent->isFrogsGoMoo; + + manageColorSchemeButton->setText(1, colorDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageColorSchemeButton->setEnabledButtons(0, !themeDownloading); + manageColorSchemeButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || colorDownloading) && !cancellingDownload && !finalizingDownload && !colorsDownloaded && parked); + manageColorSchemeButton->setEnabledButtons(2, !themeDownloading); + + manageDistanceIconPackButton->setText(1, distanceIconDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageDistanceIconPackButton->setEnabledButtons(0, !themeDownloading); + manageDistanceIconPackButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || distanceIconDownloading) && !cancellingDownload && !finalizingDownload && !distanceIconsDownloaded && parked); + manageDistanceIconPackButton->setEnabledButtons(2, !themeDownloading); + + manageIconPackButton->setText(1, iconDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageIconPackButton->setEnabledButtons(0, !themeDownloading); + manageIconPackButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || iconDownloading) && !cancellingDownload && !finalizingDownload && !iconsDownloaded && parked); + manageIconPackButton->setEnabledButtons(2, !themeDownloading); + + manageSignalAnimationButton->setText(1, signalDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageSignalAnimationButton->setEnabledButtons(0, !themeDownloading); + manageSignalAnimationButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || signalDownloading) && !cancellingDownload && !finalizingDownload && !signalsDownloaded && parked); + manageSignalAnimationButton->setEnabledButtons(2, !themeDownloading); + + manageSoundPackButton->setText(1, soundDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageSoundPackButton->setEnabledButtons(0, !themeDownloading); + manageSoundPackButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || soundDownloading) && !cancellingDownload && !finalizingDownload && !soundsDownloaded && parked); + manageSoundPackButton->setEnabledButtons(2, !themeDownloading); + + manageWheelIconsButton->setText(1, wheelDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageWheelIconsButton->setEnabledButtons(0, !themeDownloading); + manageWheelIconsButton->setEnabledButtons(1, frogpilot_scene.online && (!themeDownloading || wheelDownloading) && !cancellingDownload && !finalizingDownload && !wheelsDownloaded && parked); + manageWheelIconsButton->setEnabledButtons(2, !themeDownloading); + + parent->keepScreenOn = themeDownloading; +} + +void FrogPilotThemesPanel::updateToggles() { + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + toggle->setVisible(false); + } + } + + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + continue; + } + + bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble(); + + if (key == "DistanceIconPack") { + setVisible &= params.getBool("QOLVisuals") && params.getBool("OnroadDistanceButton"); + } + + else if (key == "RandomThemes") { + setVisible &= params.getBool("CustomThemes"); + } + + toggle->setVisible(setVisible); + + if (setVisible) { + if (customThemeKeys.contains(key)) { + toggles["CustomThemes"]->setVisible(true); + } + } + } + + openDescriptions(forceOpenDescriptions, toggles); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/theme_settings.h b/frogpilot/ui/qt/offroad/theme_settings.h new file mode 100644 index 000000000..f146d100f --- /dev/null +++ b/frogpilot/ui/qt/offroad/theme_settings.h @@ -0,0 +1,67 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotThemesPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotThemesPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +protected: + void showEvent(QShowEvent *event) override; + +signals: + void openSubPanel(); + +private: + void updateState(const UIState &s, const FrogPilotUIState &fs); + void updateToggles(); + + bool cancellingDownload; + bool colorDownloading; + bool colorsDownloaded; + bool distanceIconDownloading; + bool distanceIconsDownloaded; + bool finalizingDownload; + bool forceOpenDescriptions; + bool iconDownloading; + bool iconsDownloaded; + bool randomThemes; + bool signalDownloading; + bool signalsDownloaded; + bool soundDownloading; + bool soundsDownloaded; + bool themeDownloading; + bool wheelDownloading; + bool wheelsDownloaded; + + std::map toggles; + + QSet customThemeKeys = {"ColorScheme", "DistanceIconPack", "DownloadStatusLabel", "IconPack", "SignalAnimation", "SoundPack", "WheelIcon"}; + + QSet parentKeys; + + FrogPilotButtonsControl *manageColorSchemeButton; + FrogPilotButtonsControl *manageDistanceIconPackButton; + FrogPilotButtonsControl *manageIconPackButton; + FrogPilotButtonsControl *manageSignalAnimationButton; + FrogPilotButtonsControl *manageSoundPackButton; + FrogPilotButtonsControl *manageWheelIconsButton; + + FrogPilotSettingsWindow *parent; + + LabelControl *downloadStatusLabel; + + QDir themePacksDirectory{"/data/themes/theme_packs/"}; + QDir wheelsDirectory{"/data/themes/steering_wheels/"}; + + QString colorSchemeToDownload; + QString distanceIconPackToDownload; + QString iconPackToDownload; + QString signalAnimationToDownload; + QString soundPackToDownload; + QString wheelToDownload; + + Params params; +}; diff --git a/frogpilot/ui/qt/offroad/utilities.cc b/frogpilot/ui/qt/offroad/utilities.cc new file mode 100644 index 000000000..80ffdff49 --- /dev/null +++ b/frogpilot/ui/qt/offroad/utilities.cc @@ -0,0 +1,5 @@ +#include "frogpilot/ui/qt/offroad/utilities.h" + +FrogPilotUtilitiesPanel::FrogPilotUtilitiesPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; +} diff --git a/frogpilot/ui/qt/offroad/utilities.h b/frogpilot/ui/qt/offroad/utilities.h new file mode 100644 index 000000000..da0a50c9c --- /dev/null +++ b/frogpilot/ui/qt/offroad/utilities.h @@ -0,0 +1,17 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotUtilitiesPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotUtilitiesPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +private: + bool forceOpenDescriptions; + + FrogPilotSettingsWindow *parent; + + Params params; +}; diff --git a/frogpilot/ui/qt/offroad/vehicle_settings.cc b/frogpilot/ui/qt/offroad/vehicle_settings.cc new file mode 100644 index 000000000..7f78b2667 --- /dev/null +++ b/frogpilot/ui/qt/offroad/vehicle_settings.cc @@ -0,0 +1,281 @@ +#include "frogpilot/ui/qt/offroad/vehicle_settings.h" + +FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + QStackedLayout *vehiclesLayout = new QStackedLayout(); + addItem(vehiclesLayout); + + FrogPilotListWidget *settingsList = new FrogPilotListWidget(this); + + ScrollView *vehiclesPanel = new ScrollView(settingsList, this); + + vehiclesLayout->addWidget(vehiclesPanel); + + FrogPilotListWidget *gmList = new FrogPilotListWidget(this); + FrogPilotListWidget *hkgList = new FrogPilotListWidget(this); + FrogPilotListWidget *subaruList = new FrogPilotListWidget(this); + FrogPilotListWidget *toyotaList = new FrogPilotListWidget(this); + FrogPilotListWidget *vehicleInfoList = new FrogPilotListWidget(this); + + ScrollView *gmPanel = new ScrollView(gmList, this); + ScrollView *hkgPanel = new ScrollView(hkgList, this); + ScrollView *subaruPanel = new ScrollView(subaruList, this); + ScrollView *toyotaPanel = new ScrollView(toyotaList, this); + ScrollView *vehicleInfoPanel = new ScrollView(vehicleInfoList, this); + + vehiclesLayout->addWidget(gmPanel); + vehiclesLayout->addWidget(hkgPanel); + vehiclesLayout->addWidget(subaruPanel); + vehiclesLayout->addWidget(toyotaPanel); + vehiclesLayout->addWidget(vehicleInfoPanel); + + std::vector> vehicleToggles { + {"GMToggles", tr("General Motors Settings"), tr("FrogPilot features for General Motors vehicles."), ""}, + {"VoltSNG", tr("Stop-and-Go Hack"), tr("Force stop-and-go on the 2017 Chevy Volt."), ""}, + + {"HKGToggles", tr("Hyundai/Kia/Genesis Settings"), tr("FrogPilot features for Genesis, Hyundai, and Kia vehicles."), ""}, + {"TacoTuneHacks", tr("\"Taco Bell Run\" Torque Hack"), tr("The steering torque hack from comma's 2022 \"Taco Bell Run\". Designed to increase steering torque at low speeds for left and right turns."), ""}, + + {"SubaruToggles", tr("Subaru Settings"), tr("FrogPilot features for Subaru vehicles."), ""}, + {"SubaruSNG", tr("Stop and Go"), tr("Stop and go for supported Subaru vehicles."), ""}, + + {"ToyotaToggles", tr("Toyota/Lexus Settings"), tr("FrogPilot features for Lexus and Toyota vehicles."), ""}, + {"ToyotaDoors", tr("Automatically Lock/Unlock Doors"), tr("Automatically lock/unlock doors when shifting in and out of drive."), ""}, + {"ClusterOffset", tr("Dashboard Speed Offset"), tr("The speed offset openpilot uses to match the speed on the dashboard display."), ""}, + {"FrogsGoMoosTweak", tr("FrogsGoMoo's Personal Tweaks"), tr("Personal tweaks by FrogsGoMoo for quicker acceleration and smoother braking."), ""}, + {"LockDoorsTimer", tr("Lock Doors On Ignition Off After"), tr("Automatically lock the doors on ignition off when no one is detected in the front seats."), ""}, + {"SNGHack", tr("Stop-and-Go Hack"), tr("Force stop-and-go on Lexus/Toyota vehicles without stock stop-and-go functionality."), ""}, + + {"VehicleInfo", tr("Vehicle Info"), tr("Information about your vehicle in regards to openpilot support and functionality."), ""}, + {"HardwareDetected", tr("3rd Party Hardware Detected"), tr("Detected 3rd party hardware."), ""}, + {"BlindSpotSupport", tr("Blind Spot Support"), tr("Does openpilot use the vehicle's blind spot data?"), ""}, + {"PedalSupport", tr("comma Pedal Support"), tr("Does your vehicle support the \"comma pedal\"?"), ""}, + {"OpenpilotLongitudinal", tr("openpilot Longitudinal Support"), tr("Can openpilot control the vehicle's acceleration and braking?"), ""}, + {"RadarSupport", tr("Radar Support"), tr("Does openpilot use the vehicle's radar data alongside the device's camera for tracking lead vehicles?"), ""}, + {"SDSUSupport", tr("SDSU Support"), tr("Does your vehicle support \"SDSUs\"?"), ""}, + {"SNGSupport", tr("Stop-and-Go Support"), tr("Does your vehicle support stop-and-go driving?"), ""} + }; + + for (const auto &[param, title, desc, icon] : vehicleToggles) { + AbstractControl *vehicleToggle; + + if (param == "GMToggles") { + ButtonControl *gmButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(gmButton, &ButtonControl::clicked, [vehiclesLayout, gmPanel, this]() { + openDescriptions(forceOpenDescriptions, toggles); + vehiclesLayout->setCurrentWidget(gmPanel); + }); + vehicleToggle = gmButton; + + } else if (param == "HKGToggles") { + ButtonControl *hkgButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(hkgButton, &ButtonControl::clicked, [vehiclesLayout, hkgPanel, this]() { + openDescriptions(forceOpenDescriptions, toggles); + vehiclesLayout->setCurrentWidget(hkgPanel); + }); + vehicleToggle = hkgButton; + + } else if (param == "SubaruToggles") { + ButtonControl *subaruButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(subaruButton, &ButtonControl::clicked, [vehiclesLayout, subaruPanel, this]() { + openDescriptions(forceOpenDescriptions, toggles); + vehiclesLayout->setCurrentWidget(subaruPanel); + }); + vehicleToggle = subaruButton; + + } else if (param == "ToyotaToggles") { + ButtonControl *toyotaButton = new ButtonControl(title, tr("MANAGE"), desc); + QObject::connect(toyotaButton, &ButtonControl::clicked, [vehiclesLayout, toyotaPanel, this]() { + openDescriptions(forceOpenDescriptions, toggles); + vehiclesLayout->setCurrentWidget(toyotaPanel); + }); + vehicleToggle = toyotaButton; + } else if (param == "ToyotaDoors") { + std::vector lockToggles{"LockDoors", "UnlockDoors"}; + std::vector lockToggleNames{tr("Lock"), tr("Unlock")}; + vehicleToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, lockToggles, lockToggleNames); + } else if (param == "LockDoorsTimer") { + std::map autoLockLabels; + for (int i = 0; i <= 300; ++i) { + autoLockLabels[i] = i == 0 ? tr("Never") : QString::number(i) + tr(" seconds"); + } + vehicleToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 300, QString(), autoLockLabels, 5); + } else if (param == "ClusterOffset") { + std::vector clusterOffsetButton{"Reset"}; + FrogPilotParamValueButtonControl *clusterOffsetToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 1.000, 1.050, "x", std::map(), 0.001, false, {}, clusterOffsetButton, false, false); + QObject::connect(clusterOffsetToggle, &FrogPilotParamValueButtonControl::buttonClicked, [clusterOffsetToggle, this]() { + params.putFloat("ClusterOffset", std::stof(params.getKeyDefaultValue("ClusterOffset").value())); + clusterOffsetToggle->refresh(); + }); + vehicleToggle = clusterOffsetToggle; + + } else if (param == "VehicleInfo") { + ButtonControl *VehicleInfoButton = new ButtonControl(title, tr("VIEW"), desc); + QObject::connect(VehicleInfoButton, &ButtonControl::clicked, [vehiclesLayout, vehicleInfoPanel, this]() { + openDescriptions(forceOpenDescriptions, toggles); + vehiclesLayout->setCurrentWidget(vehicleInfoPanel); + }); + vehicleToggle = VehicleInfoButton; + } else if (vehicleInfoKeys.contains(param)) { + vehicleToggle = new LabelControl(title, "", desc); + + } else { + vehicleToggle = new ParamControl(param, title, desc, icon); + } + + toggles[param] = vehicleToggle; + + if (gmKeys.contains(param)) { + gmList->addItem(vehicleToggle); + } else if (hkgKeys.contains(param)) { + hkgList->addItem(vehicleToggle); + } else if (subaruKeys.contains(param)) { + subaruList->addItem(vehicleToggle); + } else if (toyotaKeys.contains(param)) { + toyotaList->addItem(vehicleToggle); + } else if (vehicleInfoKeys.contains(param)) { + vehicleInfoList->addItem(vehicleToggle); + } else { + settingsList->addItem(vehicleToggle); + + parentKeys.insert(param); + } + + if (ButtonControl *buttonControl = qobject_cast(vehicleToggle)) { + QObject::connect(buttonControl, &ButtonControl::clicked, this, &FrogPilotVehiclesPanel::openSubPanel); + } + + QObject::connect(vehicleToggle, &AbstractControl::hideDescriptionEvent, [this]() { + update(); + }); + QObject::connect(vehicleToggle, &AbstractControl::showDescriptionEvent, [this]() { + update(); + }); + } + + static_cast(toggles["LockDoorsTimer"])->setWarning("Warning: openpilot can't detect if keys are still inside the car, so ensure you have a spare key to prevent accidental lockouts!"); + + QSet rebootKeys = {"TacoTuneHacks"}; + for (const QString &key : rebootKeys) { + QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) { + if (started) { + if (key == "TacoTuneHacks" && state) { + if (FrogPilotConfirmationDialog::toggleReboot(this)) { + Hardware::reboot(); + } + } else if (key != "TacoTuneHacks") { + if (FrogPilotConfirmationDialog::toggleReboot(this)) { + Hardware::reboot(); + } + } + } + }); + } + + openDescriptions(forceOpenDescriptions, toggles); + + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [vehiclesLayout, vehiclesPanel, this] { + if (forceOpenDescriptions) { + openDescriptions(forceOpenDescriptions, toggles); + } + vehiclesLayout->setCurrentWidget(vehiclesPanel); + }); + QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotVehiclesPanel::updateState); +} + +void FrogPilotVehiclesPanel::showEvent(QShowEvent *event) { + if (forceOpenDescriptions) { + } + + QStringList detected; + if (parent->hasPedal) detected << "comma Pedal"; + if (parent->hasSDSU) detected << "SDSU"; + if (parent->hasZSS) detected << "ZSS"; + static_cast(toggles["HardwareDetected"])->setText(detected.isEmpty() ? tr("None") : detected.join(", ")); + + static_cast(toggles["BlindSpotSupport"])->setText(parent->hasBSM ? tr("Yes") : tr("No")); + static_cast(toggles["OpenpilotLongitudinal"])->setText(parent->hasOpenpilotLongitudinal ? tr("Yes") : tr("No")); + static_cast(toggles["PedalSupport"])->setText(parent->canUsePedal ? tr("Yes") : tr("No")); + static_cast(toggles["RadarSupport"])->setText(parent->hasRadar ? tr("Yes") : tr("No")); + static_cast(toggles["SDSUSupport"])->setText(parent->canUseSDSU ? tr("Yes") : tr("No")); + static_cast(toggles["SNGSupport"])->setText(parent->hasSNG ? tr("Yes") : tr("No")); + + updateToggles(); +} + +void FrogPilotVehiclesPanel::updateState(const UIState &s) { + if (!isVisible()) { + return; + } + + started = s.scene.started; +} + +void FrogPilotVehiclesPanel::updateToggles() { + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + toggle->setVisible(false); + } + } + + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + continue; + } + + bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble(); + + if (gmKeys.contains(key)) { + setVisible &= parent->isGM; + } else if (hkgKeys.contains(key)) { + setVisible &= parent->isHKG; + } else if (subaruKeys.contains(key)) { + setVisible &= parent->isSubaru; + } else if (toyotaKeys.contains(key)) { + setVisible &= parent->isToyota; + } else if (vehicleInfoKeys.contains(key)) { + setVisible = true; + } + + if (longitudinalKeys.contains(key)) { + setVisible &= parent->hasOpenpilotLongitudinal; + } + + if (key == "SNGHack") { + setVisible &= !parent->hasSNG; + } + + else if (key == "SubaruSNG") { + setVisible &= parent->hasSNG; + } + + else if (key == "TacoTuneHacks") { + setVisible &= parent->isHKGCanFd; + } + + else if (key == "VoltSNG") { + setVisible &= parent->isVolt && !parent->hasSNG; + } + + toggle->setVisible(setVisible); + + if (setVisible) { + if (gmKeys.contains(key)) { + toggles["GMToggles"]->setVisible(true); + } else if (hkgKeys.contains(key)) { + toggles["HKGToggles"]->setVisible(true); + } else if (subaruKeys.contains(key)) { + toggles["SubaruToggles"]->setVisible(true); + } else if (toyotaKeys.contains(key)) { + toggles["ToyotaToggles"]->setVisible(true); + } else if (vehicleInfoKeys.contains(key)) { + toggles["VehicleInfo"]->setVisible(true); + } + } + } + + openDescriptions(forceOpenDescriptions, toggles); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/vehicle_settings.h b/frogpilot/ui/qt/offroad/vehicle_settings.h new file mode 100644 index 000000000..4716593d7 --- /dev/null +++ b/frogpilot/ui/qt/offroad/vehicle_settings.h @@ -0,0 +1,38 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotVehiclesPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotVehiclesPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +signals: + void openSubPanel(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void updateState(const UIState &s); + void updateToggles(); + + bool forceOpenDescriptions; + bool started; + + std::map toggles; + + QSet gmKeys = {"VoltSNG"}; + QSet hkgKeys = {"TacoTuneHacks"}; + QSet longitudinalKeys = {"FrogsGoMoosTweak", "SNGHack", "VoltSNG"}; + QSet subaruKeys = {"SubaruSNG"}; + QSet toyotaKeys = {"ClusterOffset", "FrogsGoMoosTweak", "LockDoorsTimer", "SNGHack", "ToyotaDoors"}; + QSet vehicleInfoKeys = {"BlindSpotSupport", "HardwareDetected", "OpenpilotLongitudinal", "PedalSupport", "RadarSupport", "SDSUSupport", "SNGSupport"}; + + QSet parentKeys; + + FrogPilotSettingsWindow *parent; + + Params params; +}; diff --git a/frogpilot/ui/qt/offroad/visual_settings.cc b/frogpilot/ui/qt/offroad/visual_settings.cc new file mode 100644 index 000000000..9b4905ffa --- /dev/null +++ b/frogpilot/ui/qt/offroad/visual_settings.cc @@ -0,0 +1,328 @@ +#include "frogpilot/ui/qt/offroad/visual_settings.h" + +FrogPilotVisualsPanel::FrogPilotVisualsPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + QStackedLayout *visualsLayout = new QStackedLayout(); + addItem(visualsLayout); + + FrogPilotListWidget *visualsList = new FrogPilotListWidget(this); + + ScrollView *visualsPanel = new ScrollView(visualsList, this); + + visualsLayout->addWidget(visualsPanel); + + FrogPilotListWidget *advancedCustomList = new FrogPilotListWidget(this); + FrogPilotListWidget *customUIList = new FrogPilotListWidget(this); + FrogPilotListWidget *modelUIList = new FrogPilotListWidget(this); + FrogPilotListWidget *navigationUIList = new FrogPilotListWidget(this); + FrogPilotListWidget *qualityOfLifeList = new FrogPilotListWidget(this); + + ScrollView *advancedCustomPanel = new ScrollView(advancedCustomList, this); + ScrollView *customUIPanel = new ScrollView(customUIList, this); + ScrollView *modelUIPanel = new ScrollView(modelUIList, this); + ScrollView *navigationUIPanel = new ScrollView(navigationUIList, this); + ScrollView *qualityOfLifePanel = new ScrollView(qualityOfLifeList, this); + + visualsLayout->addWidget(advancedCustomPanel); + visualsLayout->addWidget(customUIPanel); + visualsLayout->addWidget(modelUIPanel); + visualsLayout->addWidget(navigationUIPanel); + visualsLayout->addWidget(qualityOfLifePanel); + + const std::vector> visualToggles { + {"AdvancedCustomUI", tr("Advanced UI Controls"), tr("Advanced visual changes to fine-tune how the driving screen looks."), "../../frogpilot/assets/toggle_icons/icon_advanced_device.png"}, + {"HideSpeed", tr("Hide Current Speed"), tr("Hide the current speed from the driving screen."), ""}, + {"HideLeadMarker", tr("Hide Lead Marker"), tr("Hide the lead-vehicle marker from the driving screen."), ""}, + {"HideMaxSpeed", tr("Hide Max Speed"), tr("Hide the max speed from the driving screen."), ""}, + {"HideAlerts", tr("Hide Non-Critical Alerts"), tr("Hide non-critical alerts from the driving screen."), ""}, + {"HideSpeedLimit", tr("Hide Speed Limits"), tr("Hide posted speed limits from the driving screen."), ""}, + {"WheelSpeed", tr("Use Wheel Speed"), tr("Use the vehicle's wheel speed instead of the cluster speed. This is purely a visual change and doesn't impact how openpilot drives!"), ""}, + + {"CustomUI", tr("Driving Screen Widgets"), tr("Custom FrogPilot widgets for the driving screen."), "../assets/icons/calibration.png"}, + {"AccelerationPath", tr("Acceleration Path"), tr("Color the driving path by planned acceleration and braking."), ""}, + {"AdjacentPath", tr("Adjacent Lanes"), tr("Show the driving paths for the left and right lanes."), ""}, + {"BlindSpotPath", tr("Blind Spot Path"), tr("Show a red path when a vehicle is in that lane's blind spot."), ""}, + {"Compass", tr("Compass"), tr("Show the current driving direction with a simple on-screen compass."), ""}, + {"OnroadDistanceButton", tr("Driving Personality Button"), tr("Control and view the current driving personality via a driving screen widget."), ""}, + {"PedalsOnUI", tr("Gas / Brake Pedal Indicators"), tr("On-screen gas and brake indicators.

Dynamic: Opacity changes according to how much openpilot is accelerating or braking
Static: Full when active, dim when not"), ""}, + {"RotatingWheel", tr("Rotating Steering Wheel"), tr("Rotate the driving screen wheel with the physical steering wheel."), ""}, + + {"ModelUI", tr("Model UI"), tr("Model visualizations for the driving path, lane lines, path edges, and road edges."), "../../frogpilot/assets/toggle_icons/icon_road.png"}, + {"DynamicPathWidth", tr("Dynamic Path Width"), tr("Change the path width based on engagement.

Fully Engaged: 100%
Always On Lateral: 75%
Disengaged: 50%"), ""}, + {"LaneLinesWidth", tr("Lane Lines Width"), tr("Set the lane-line thickness.

Default matches the MUTCD lane-line width standard of 4 inches."), ""}, + {"PathEdgeWidth", tr("Path Edges Width"), tr("Set the driving-path edge width that represents different driving modes and statuses.

Default is 20% of the total path width.

Color Guide:

- Light Blue: Always On Lateral
- Green: Default
- Orange: Experimental Mode
- Red: Traffic Mode
- Yellow: Conditional Experimental Mode overridden"), ""}, + {"PathWidth", tr("Path Width"), tr("Set the driving-path width.

Default (6.1 feet) matches the width of a 2019 Lexus ES 350."), ""}, + {"RoadEdgesWidth", tr("Road Edges Width"), tr("Set the road-edge thickness.

Default matches half of the MUTCD lane-line width standard of 4 inches."), ""}, + + {"NavigationUI", tr("Navigation Widgets"), tr("Speed limits, and other navigation widgets."), "../../frogpilot/assets/toggle_icons/icon_map.png"}, + {"RoadNameUI", tr("Road Name"), tr("Display the road name at the bottom of the driving screen using data from \"OpenStreetMap (OSM)\"."), ""}, + {"ShowSpeedLimits", tr("Show Speed Limits"), tr("Show speed limits in the top-left corner of the driving screen. Uses data from the car's dashboard (if supported) and \"OpenStreetMap (OSM)\"."), ""}, + {"SLCMapboxFiller", tr("Show Speed Limits from Mapbox"), tr("Use Mapbox speed-limit data when no other source is available."), ""}, + {"UseVienna", tr("Use Vienna-Style Speed Signs"), tr("Show Vienna-style (EU) speed-limit signs instead of MUTCD (US)."), ""}, + + {"QOLVisuals", tr("Quality of Life"), tr("Miscellaneous visual changes to fine-tune how the driving screen looks."), "../../frogpilot/assets/toggle_icons/icon_quality_of_life.png"}, + {"CameraView", tr("Camera View"), tr("Select the active camera view. This is purely a visual change and doesn't impact how openpilot drives!"), ""}, + {"DriverCamera", tr("Show Driver Camera When In Reverse"), tr("Show the driver camera feed when the vehicle is in reverse."), ""}, + {"StoppedTimer", tr("Stopped Timer"), tr("Show a timer when stopped in place of the current speed to indicate how long the vehicle has been stopped."), ""} + }; + + for (const auto &[param, title, desc, icon] : visualToggles) { + AbstractControl *visualToggle; + + if (param == "AdvancedCustomUI") { + FrogPilotManageControl *advancedCustomUIToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(advancedCustomUIToggle, &FrogPilotManageControl::manageButtonClicked, [visualsLayout, advancedCustomPanel]() { + visualsLayout->setCurrentWidget(advancedCustomPanel); + }); + visualToggle = advancedCustomUIToggle; + + } else if (param == "CustomUI") { + FrogPilotManageControl *customUIToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(customUIToggle, &FrogPilotManageControl::manageButtonClicked, [visualsLayout, customUIPanel]() { + visualsLayout->setCurrentWidget(customUIPanel); + }); + visualToggle = customUIToggle; + } else if (param == "PedalsOnUI") { + std::vector pedalsToggles{"DynamicPedalsOnUI", "StaticPedalsOnUI"}; + std::vector pedalsToggleNames{tr("Dynamic"), tr("Static")}; + FrogPilotButtonToggleControl *pedalsToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, pedalsToggles, pedalsToggleNames, true); + QObject::connect(pedalsToggle, &FrogPilotButtonToggleControl::buttonClicked, [this](int id) { + if (id == 0) { + params.putBool("StaticPedalsOnUI", false); + } else if (id == 1) { + params.putBool("DynamicPedalsOnUI", false); + } + }); + visualToggle = pedalsToggle; + + } else if (param == "ModelUI") { + FrogPilotManageControl *modelUIToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(modelUIToggle, &FrogPilotManageControl::manageButtonClicked, [visualsLayout, modelUIPanel]() { + visualsLayout->setCurrentWidget(modelUIPanel); + }); + visualToggle = modelUIToggle; + } else if (param == "LaneLinesWidth" || param == "RoadEdgesWidth") { + visualToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 24, tr(" inches")); + } else if (param == "PathEdgeWidth") { + std::map pathEdgeLabels; + for (int i = 0; i <= 100; ++i) { + pathEdgeLabels[i] = i == 0 ? tr("Off") : QString::number(i) + "%"; + } + visualToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 100, QString(), pathEdgeLabels); + } else if (param == "PathWidth") { + visualToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, tr(" feet"), std::map(), 0.1); + + } else if (param == "NavigationUI") { + FrogPilotManageControl *navigationUIToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(navigationUIToggle, &FrogPilotManageControl::manageButtonClicked, [visualsLayout, navigationUIPanel]() { + visualsLayout->setCurrentWidget(navigationUIPanel); + }); + visualToggle = navigationUIToggle; + + } else if (param == "QOLVisuals") { + FrogPilotManageControl *qolToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(qolToggle, &FrogPilotManageControl::manageButtonClicked, [visualsLayout, qualityOfLifePanel]() { + visualsLayout->setCurrentWidget(qualityOfLifePanel); + }); + visualToggle = qolToggle; + } else if (param == "CameraView") { + std::vector cameraOptions{tr("Auto"), tr("Driver"), tr("Standard"), tr("Wide")}; + ButtonParamControl *cameraSelection = new ButtonParamControl(param, title, desc, icon, cameraOptions); + visualToggle = cameraSelection; + + } else { + visualToggle = new ParamControl(param, title, desc, icon); + } + + toggles[param] = visualToggle; + + if (advancedCustomOnroadUIKeys.contains(param)) { + advancedCustomList->addItem(visualToggle); + } else if (customOnroadUIKeys.contains(param)) { + customUIList->addItem(visualToggle); + } else if (modelUIKeys.contains(param)) { + modelUIList->addItem(visualToggle); + } else if (navigationUIKeys.contains(param)) { + navigationUIList->addItem(visualToggle); + } else if (qualityOfLifeKeys.contains(param)) { + qualityOfLifeList->addItem(visualToggle); + } else { + visualsList->addItem(visualToggle); + + parentKeys.insert(param); + } + + if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast(visualToggle)) { + QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { + emit openSubPanel(); + openDescriptions(forceOpenDescriptions, toggles); + }); + } + + QObject::connect(visualToggle, &AbstractControl::hideDescriptionEvent, [this]() { + update(); + }); + QObject::connect(visualToggle, &AbstractControl::showDescriptionEvent, [this]() { + update(); + }); + } + + QSet forceUpdateKeys = {"HideLeadMarker", "ShowSpeedLimits"}; + for (const QString &key : forceUpdateKeys) { + QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, this, &FrogPilotVisualsPanel::updateToggles); + } + + openDescriptions(forceOpenDescriptions, toggles); + + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [visualsLayout, visualsPanel, this] { + openDescriptions(forceOpenDescriptions, toggles); + visualsLayout->setCurrentWidget(visualsPanel); + }); + QObject::connect(parent, &FrogPilotSettingsWindow::closeSubSubPanel, [this]() { + openDescriptions(forceOpenDescriptions, toggles); + }); + QObject::connect(parent, &FrogPilotSettingsWindow::updateMetric, this, &FrogPilotVisualsPanel::updateMetric); +} + +void FrogPilotVisualsPanel::showEvent(QShowEvent *event) { + updateToggles(); +} + +void FrogPilotVisualsPanel::updateMetric(bool metric, bool bootRun) { + static bool previousMetric; + if (metric != previousMetric && !bootRun) { + double distanceConversion = metric ? FOOT_TO_METER : METER_TO_FOOT; + double smallDistanceConversion = metric ? INCH_TO_CM : CM_TO_INCH; + + params.putIntNonBlocking("LaneLinesWidth", params.getInt("LaneLinesWidth") * smallDistanceConversion); + params.putIntNonBlocking("RoadEdgesWidth", params.getInt("RoadEdgesWidth") * smallDistanceConversion); + + params.putFloatNonBlocking("PathWidth", params.getFloat("PathWidth") * distanceConversion); + } + previousMetric = metric; + + static std::map imperialDistanceLabels; + static std::map imperialSmallDistanceLabels; + static std::map metricDistanceLabels; + static std::map metricSmallDistanceLabels; + + static bool labelsInitialized = false; + if (!labelsInitialized) { + for (int i = 0; i <= 10; ++i) { + imperialDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" foot") : QString::number(i) + tr(" feet"); + } + + for (int i = 0; i <= 24; ++i) { + imperialSmallDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" inch") : QString::number(i) + tr(" inches"); + } + + for (float i = 0.0f; i <= 3.0f; i += 0.1f) { + metricDistanceLabels[i] = i == 0.0f ? tr("Off") : i == 1.0 ? QString::number(i) + tr(" meter") : QString::number(i, 'f', 1) + tr(" meters"); + } + + for (int i = 0; i <= 60; ++i) { + metricSmallDistanceLabels[i] = i == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" centimeter") : QString::number(i) + tr(" centimeters"); + } + + labelsInitialized = true; + } + + FrogPilotParamValueControl *laneLinesWidthToggle = static_cast(toggles["LaneLinesWidth"]); + FrogPilotParamValueControl *pathWidthToggle = static_cast(toggles["PathWidth"]); + FrogPilotParamValueControl *roadEdgesWidthToggle = static_cast(toggles["RoadEdgesWidth"]); + + if (metric) { + laneLinesWidthToggle->setDescription(tr("Set the lane-line thickness.

Default matches the MUTCD lane-line width standard of 10 centimeters.")); + pathWidthToggle->setDescription(tr("Set the driving-path width.

Default (1.9 meters) matches the width of a 2019 Lexus ES 350.")); + roadEdgesWidthToggle->setDescription(tr("Set the road-edge thickness.

Default matches half of the MUTCD lane-line width standard of 10 centimeters.")); + + laneLinesWidthToggle->updateControl(0, 60, metricSmallDistanceLabels); + roadEdgesWidthToggle->updateControl(0, 60, metricSmallDistanceLabels); + + pathWidthToggle->updateControl(0, 3, metricDistanceLabels); + } else { + laneLinesWidthToggle->setDescription(tr("Set the lane-line thickness.

Default matches the MUTCD lane-line width standard of 4 inches.")); + pathWidthToggle->setDescription(tr("Set the driving-path width.

Default (6.1 feet) matches the width of a 2019 Lexus ES 350.")); + roadEdgesWidthToggle->setDescription(tr("Set the road-edge thickness.

Default matches half of the MUTCD lane-line width standard of 4 inches.")); + + laneLinesWidthToggle->updateControl(0, 24, imperialSmallDistanceLabels); + roadEdgesWidthToggle->updateControl(0, 24, imperialSmallDistanceLabels); + + pathWidthToggle->updateControl(0, 10, imperialDistanceLabels); + } +} + +void FrogPilotVisualsPanel::updateToggles() { + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + toggle->setVisible(false); + } + } + + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + continue; + } + + bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble(); + + if (key == "AccelerationPath") { + setVisible &= parent->hasOpenpilotLongitudinal; + } + + else if (key == "BlindSpotPath") { + setVisible &= parent->hasBSM; + } + + else if (key == "HideLeadMarker") { + setVisible &= parent->hasOpenpilotLongitudinal; + } + + else if (key == "HideSpeedLimit") { + setVisible &= parent->hasOpenpilotLongitudinal && params.getBool("SpeedLimitController"); + } + + else if (key == "OnroadDistanceButton") { + setVisible &= parent->hasOpenpilotLongitudinal; + } + + else if (key == "PedalsOnUI") { + setVisible &= parent->hasOpenpilotLongitudinal; + } + + else if (key == "ShowSpeedLimits") { + setVisible &= !params.getBool("SpeedLimitController") || !parent->hasOpenpilotLongitudinal; + } + + else if (key == "SLCMapboxFiller") { + setVisible &= params.getBool("ShowSpeedLimits"); + setVisible &= !params.getBool("SpeedLimitController") || !parent->hasOpenpilotLongitudinal; + setVisible &= !params.get("MapboxSecretKey").empty(); + } + + else if (key == "UseVienna") { + setVisible &= params.getBool("ShowSpeedLimits") || params.getBool("SpeedLimitController"); + } + + toggle->setVisible(setVisible); + + if (setVisible) { + if (advancedCustomOnroadUIKeys.contains(key)) { + toggles["AdvancedCustomUI"]->setVisible(true); + } else if (customOnroadUIKeys.contains(key)) { + toggles["CustomUI"]->setVisible(true); + } else if (modelUIKeys.contains(key)) { + toggles["ModelUI"]->setVisible(true); + } else if (navigationUIKeys.contains(key)) { + toggles["NavigationUI"]->setVisible(true); + } else if (qualityOfLifeKeys.contains(key)) { + toggles["QOLVisuals"]->setVisible(true); + } + } + } + + openDescriptions(forceOpenDescriptions, toggles); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/visual_settings.h b/frogpilot/ui/qt/offroad/visual_settings.h new file mode 100644 index 000000000..9ea1d36b8 --- /dev/null +++ b/frogpilot/ui/qt/offroad/visual_settings.h @@ -0,0 +1,37 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotVisualsPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotVisualsPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +signals: + void openSubPanel(); + void openSubSubPanel(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void updateMetric(bool metric, bool bootRun); + void updateToggles(); + + bool forceOpenDescriptions; + + std::map toggles; + + QSet advancedCustomOnroadUIKeys = {"HideAlerts", "HideLeadMarker", "HideMaxSpeed", "HideSpeed", "HideSpeedLimit", "WheelSpeed"}; + QSet customOnroadUIKeys = {"AccelerationPath", "AdjacentPath", "BlindSpotPath", "Compass", "OnroadDistanceButton", "PedalsOnUI", "RotatingWheel"}; + QSet modelUIKeys = {"DynamicPathWidth", "LaneLinesWidth", "PathEdgeWidth", "PathWidth", "RoadEdgesWidth"}; + QSet navigationUIKeys = {"RoadNameUI", "ShowSpeedLimits", "SLCMapboxFiller", "UseVienna"}; + QSet qualityOfLifeKeys = {"CameraView", "DriverCamera", "StoppedTimer"}; + + QSet parentKeys; + + FrogPilotSettingsWindow *parent; + + Params params; +}; diff --git a/frogpilot/ui/qt/offroad/wheel_settings.cc b/frogpilot/ui/qt/offroad/wheel_settings.cc new file mode 100644 index 000000000..c59251f73 --- /dev/null +++ b/frogpilot/ui/qt/offroad/wheel_settings.cc @@ -0,0 +1,84 @@ +#include "frogpilot/ui/qt/offroad/wheel_settings.h" + +FrogPilotWheelPanel::FrogPilotWheelPanel(FrogPilotSettingsWindow *parent, bool forceOpen) : FrogPilotListWidget(parent), parent(parent) { + forceOpenDescriptions = forceOpen; + + const std::vector> wheelToggles { + {"DistanceButtonControl", tr("Distance Button"), tr("Action performed when the \"Distance\" button is pressed."), "../../frogpilot/assets/toggle_icons/icon_mute.png"}, + {"LongDistanceButtonControl", tr("Distance Button (Long Press)"), tr("Action performed when the \"Distance\" button is pressed for more than 0.5 seconds."), "../../frogpilot/assets/toggle_icons/icon_mute.png"}, + {"VeryLongDistanceButtonControl", tr("Distance Button (Very Long Press)"), tr("Action performed when the \"Distance\" button is pressed for more than 2.5 seconds."), "../../frogpilot/assets/toggle_icons/icon_mute.png"}, + {"LKASButtonControl", tr("LKAS Button"), tr("Action performed when the \"LKAS\" button is pressed."), "../../frogpilot/assets/toggle_icons/icon_mute.png"} + }; + + for (const auto &[param, title, desc, icon] : wheelToggles) { + QMap functionsMap { + {0, tr("No Action")}, + {3, tr("Pause Steering")} + }; + + QMap longitudinalFunctionsMap { + {1, tr("Change \"Personality Profile\"")}, + {2, tr("Force openpilot to Coast")}, + {4, tr("Pause Acceleration/Braking")}, + {5, tr("Toggle \"Experimental Mode\" On/Off")}, + {6, tr("Toggle \"Traffic Mode\" On/Off")} + }; + + ButtonControl *wheelToggle = new ButtonControl(title, tr("SELECT"), desc); + QObject::connect(wheelToggle, &ButtonControl::clicked, [functionsMap, longitudinalFunctionsMap, key = param, parent, wheelToggle, this]() mutable { + if (parent->hasOpenpilotLongitudinal) { + QMap::const_iterator it; + for (it = longitudinalFunctionsMap.constBegin(); it != longitudinalFunctionsMap.constEnd(); ++it) { + functionsMap[it.key()] = it.value(); + } + } + + QString selection = MultiOptionDialog::getSelection(tr("Select a function to assign to this button"), functionsMap.values(), functionsMap[params.getInt(key.toStdString())], this); + if (!selection.isEmpty()) { + params.putInt(key.toStdString(), functionsMap.key(selection)); + + wheelToggle->setValue(selection); + } + }); + QMap mergedFunctionsMap = functionsMap; + QMap::const_iterator it; + for (it = longitudinalFunctionsMap.constBegin(); it != longitudinalFunctionsMap.constEnd(); ++it) { + mergedFunctionsMap[it.key()] = it.value(); + } + wheelToggle->setValue(mergedFunctionsMap[params.getInt(param.toStdString())]); + + toggles[param] = wheelToggle; + + addItem(wheelToggle); + + QObject::connect(wheelToggle, &AbstractControl::hideDescriptionEvent, [this]() { + update(); + }); + QObject::connect(wheelToggle, &AbstractControl::showDescriptionEvent, [this]() { + update(); + }); + } + + openDescriptions(forceOpenDescriptions, toggles); +} + +void FrogPilotWheelPanel::showEvent(QShowEvent *event) { + updateToggles(); +} + +void FrogPilotWheelPanel::updateToggles() { + for (auto &[key, toggle] : toggles) { + bool setVisible = parent->tuningLevel >= parent->frogpilotToggleLevels[key].toDouble(); + + if (key == "LKASButtonControl") { + setVisible &= !parent->isSubaru; + setVisible &= !parent->lkasAllowedForAOL || !(params.getBool("AlwaysOnLateral") && params.getBool("AlwaysOnLateralLKAS")); + } + + toggle->setVisible(setVisible); + } + + openDescriptions(forceOpenDescriptions, toggles); + + update(); +} diff --git a/frogpilot/ui/qt/offroad/wheel_settings.h b/frogpilot/ui/qt/offroad/wheel_settings.h new file mode 100644 index 000000000..458c72bd2 --- /dev/null +++ b/frogpilot/ui/qt/offroad/wheel_settings.h @@ -0,0 +1,24 @@ +#pragma once + +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class FrogPilotWheelPanel : public FrogPilotListWidget { + Q_OBJECT + +public: + explicit FrogPilotWheelPanel(FrogPilotSettingsWindow *parent, bool forceOpen = false); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void updateToggles(); + + bool forceOpenDescriptions; + + std::map toggles; + + FrogPilotSettingsWindow *parent; + + Params params; +}; diff --git a/frogpilot/ui/qt/widgets/frogpilot_controls.cc b/frogpilot/ui/qt/widgets/frogpilot_controls.cc index e35184bfe..1edea9ca7 100644 --- a/frogpilot/ui/qt/widgets/frogpilot_controls.cc +++ b/frogpilot/ui/qt/widgets/frogpilot_controls.cc @@ -2,6 +2,16 @@ #include "frogpilot/ui/frogpilot_ui.h" +bool FrogPilotConfirmationDialog::toggleReboot(QWidget *parent) { + ConfirmationDialog d(tr("Reboot required to take effect."), tr("Reboot Now"), tr("Reboot Later"), false, parent); + return d.exec(); +} + +bool FrogPilotConfirmationDialog::yesorno(const QString &prompt_text, QWidget *parent) { + ConfirmationDialog d(prompt_text, tr("Yes"), tr("No"), false, parent); + return d.exec(); +} + void clearMovie(QSharedPointer &movie, QWidget *parent) { if (!movie) { return; @@ -92,3 +102,13 @@ void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer parent->update(); } + +void openDescriptions(bool forceOpenDescriptions, std::map toggles) { + if (forceOpenDescriptions) { + for (auto &[key, toggle] : toggles) { + if (key != "CESpeed") { + toggle->showDescription(); + } + } + } +} diff --git a/frogpilot/ui/qt/widgets/frogpilot_controls.h b/frogpilot/ui/qt/widgets/frogpilot_controls.h index f930059c9..c73decb3b 100644 --- a/frogpilot/ui/qt/widgets/frogpilot_controls.h +++ b/frogpilot/ui/qt/widgets/frogpilot_controls.h @@ -17,3 +17,622 @@ void loadGif(const QString &gifPath, QSharedPointer &movie, const QSize &size, QWidget *parent); void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer &movie, const QSize &size, QWidget *parent); +void openDescriptions(bool forceOpenDescriptions, std::map toggles); + +const QString buttonStyle = R"( + QPushButton { + padding: 0px 25px 0px 25px; + border-radius: 50px; + font-size: 35px; + font-weight: 500; + height: 100px; + color: #E4E4E4; + background-color: #393939; + } + QPushButton:pressed { + background-color: #4a4a4a; + } + QPushButton:checked:enabled { + background-color: #33Ab4C; + } + QPushButton:disabled { + color: #33E4E4E4; + } +)"; + +class FrogPilotConfirmationDialog : public ConfirmationDialog { + Q_OBJECT + +public: + explicit FrogPilotConfirmationDialog(const QString &prompt_text, const QString &confirm_text, + const QString &cancel_text, const bool rich, QWidget *parent); + static bool toggleReboot(QWidget *parent); + static bool yesorno(const QString &prompt_text, QWidget *parent); +}; + +class FrogPilotListWidget : public QWidget { + Q_OBJECT + public: + explicit FrogPilotListWidget(QWidget *parent = 0) : QWidget(parent), outer_layout(this) { + outer_layout.setMargin(0); + outer_layout.setSpacing(0); + outer_layout.addLayout(&inner_layout); + inner_layout.setMargin(0); + inner_layout.setSpacing(25); // default spacing is 25 + outer_layout.addStretch(); + } + inline void addItem(QWidget *w, bool expanding = false) { + w->setSizePolicy(QSizePolicy::Preferred, expanding ? QSizePolicy::Expanding : QSizePolicy::Fixed); + inner_layout.addWidget(w); + } + inline void addItem(QLayout *layout) { inner_layout.addLayout(layout); } + inline void insertItem(int index, QWidget *w, bool expanding = false) { + w->setSizePolicy(QSizePolicy::Preferred, expanding ? QSizePolicy::Expanding : QSizePolicy::Fixed); + inner_layout.insertWidget(index, w); + } + inline void setSpacing(int spacing) { inner_layout.setSpacing(spacing); } + + void clear() { + while (QLayoutItem *child = inner_layout.takeAt(0)) { + if (child->widget()) { + child->widget()->deleteLater(); + } + delete child; + } + outer_layout.addStretch(); + } + +private: + void paintEvent(QPaintEvent *) override { + QPainter p(this); + p.setPen(Qt::gray); + for (int i = 0; i < inner_layout.count() - 1; ++i) { + QWidget *widget = inner_layout.itemAt(i)->widget(); + + QWidget *nextWidget = nullptr; + for (int j = i + 1; j < inner_layout.count(); ++j) { + nextWidget = inner_layout.itemAt(j)->widget(); + if (nextWidget != nullptr && nextWidget->isVisible()) { + break; + } + } + + if (widget == nullptr || (widget->isVisible() && nextWidget->isVisible())) { + QRect r = inner_layout.itemAt(i)->geometry(); + int bottom = r.bottom() + inner_layout.spacing() / 2; + p.drawLine(r.left() + 40, bottom, r.right() - 40, bottom); + } + } + } + QVBoxLayout outer_layout; + QVBoxLayout inner_layout; +}; + +class FrogPilotButtonControl : public ParamControl { + Q_OBJECT +public: + FrogPilotButtonControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + const std::vector &button_texts, bool checkable = false, + bool exclusive = false, int minimum_button_width = 225) : ParamControl(param, title, desc, icon) { + key = param.toStdString(); + + button_group = new QButtonGroup(this); + button_group->setExclusive(exclusive); + for (int i = 0; i < button_texts.size(); i++) { + QPushButton *button = new QPushButton(button_texts[i], this); + button->setCheckable(checkable); + button->setStyleSheet(buttonStyle); + button->setMinimumWidth(minimum_button_width); + hlayout->addWidget(button); + button_group->addButton(button, i); + } + + hlayout->addWidget(&toggle); + + QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) { + emit buttonClicked(id); + }); + + QObject::connect(this, &ToggleControl::toggleFlipped, this, &FrogPilotButtonControl::refresh); + + refresh(); + } + + virtual void refresh() { + bool state = params.getBool(key); + if (state != toggle.on) { + toggle.togglePosition(); + } + + for (QAbstractButton *button : button_group->buttons()) { + button->setEnabled(state); + } + } + + void clearCheckedButtons() { + bool original_exclusive = button_group->exclusive(); + + button_group->setExclusive(false); + + for (QAbstractButton *button : button_group->buttons()) { + button->setChecked(false); + } + + button_group->setExclusive(original_exclusive); + } + + void setCheckedButton(int id) { + if (QAbstractButton *button = button_group->button(id)) { + button->setChecked(true); + } + } + + void setEnabledButton(int id, bool enable) { + if (QAbstractButton *button = button_group->button(id)) { + button->setEnabled(enable); + } + } + + void setVisibleButton(int id, bool visible) { + if (QAbstractButton *button = button_group->button(id)) { + button->setVisible(visible); + } + } + +signals: + void buttonClicked(int id); + +protected: + std::string key; + + Params params; + + QButtonGroup *button_group; +}; + +class FrogPilotButtonsControl : public AbstractControl { + Q_OBJECT +public: + FrogPilotButtonsControl(const QString &title, const QString &desc, const QString &icon, + const std::vector &button_texts, const bool &checkable = false, const bool &exclusive = true, + const int minimum_button_width = 225) : AbstractControl(title, desc, icon) { + button_group = new QButtonGroup(this); + button_group->setExclusive(exclusive); + for (int i = 0; i < button_texts.size(); i++) { + QPushButton *button = new QPushButton(button_texts[i], this); + button->installEventFilter(this); + button->setCheckable(checkable); + button->setStyleSheet(buttonStyle); + button->setMinimumWidth(minimum_button_width); + hlayout->addWidget(button); + button_group->addButton(button, i); + } + + QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) { + emit buttonClicked(id); + }); + } + + void clearCheckedButtons() { + bool original_exclusive = button_group->exclusive(); + + button_group->setExclusive(false); + + for (QAbstractButton *button : button_group->buttons()) { + button->setChecked(false); + } + + button_group->setExclusive(original_exclusive); + } + + void setCheckedButton(int id) { + if (QAbstractButton *button = button_group->button(id)) { + button->setChecked(true); + } + } + + void setEnabled(bool enable) { + for (QAbstractButton *button : button_group->buttons()) { + button->setEnabled(enable); + } + } + + void setEnabledButtons(int id, bool enable) { + if (QAbstractButton *button = button_group->button(id)) { + button->setEnabled(enable); + } + } + + void setText(int id, const QString &text) { + if (QAbstractButton *button = button_group->button(id)) { + button->setText(text); + } + } + + void setVisibleButton(int id, bool visible) { + if (QAbstractButton *button = button_group->button(id)) { + button->setVisible(visible); + } + } + +signals: + void buttonClicked(int id); + void disabledButtonClicked(int id); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override { + if (event->type() == QEvent::MouseButtonPress) { + QPushButton *button = qobject_cast(obj); + if (button && !button->isEnabled()) { + emit disabledButtonClicked(button_group->id(button)); + } + } + return AbstractControl::eventFilter(obj, event); + } + +private: + QButtonGroup *button_group; +}; + +class FrogPilotButtonToggleControl : public FrogPilotButtonControl { + Q_OBJECT +public: + FrogPilotButtonToggleControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + const std::vector &button_params, const std::vector &button_texts, + bool exclusive = false, int minimum_button_width = 225) + : FrogPilotButtonControl(param, title, desc, icon, button_texts, true, exclusive, minimum_button_width), button_params(button_params) { + + for (int i = 0; i < button_texts.size(); i++) { + button_group->buttons()[i]->setChecked(params.getBool(button_params[i].toStdString())); + } + + QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) { + params.putBool(button_params[id].toStdString(), button_group->button(id)->isChecked()); + emit buttonClicked(id); + }); + } + + void refresh() override { + FrogPilotButtonControl::refresh(); + + for (int i = 0; i < button_group->buttons().size(); i++) { + QAbstractButton *button = button_group->button(i); + button->setChecked(params.getBool(button_params[i].toStdString())); + } + } + +private: + std::vector button_params; +}; + +class FrogPilotManageControl : public ParamControl { + Q_OBJECT +public: + FrogPilotManageControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon) : ParamControl(param, title, desc, icon) { + key = param.toStdString(); + + manageButton = new ButtonControl("", tr("MANAGE"), "", this); + + hlayout->insertWidget(hlayout->indexOf(&toggle) - 1, manageButton); + + QObject::connect(manageButton, &ButtonControl::clicked, this, &FrogPilotManageControl::manageButtonClicked); + QObject::connect(this, &ToggleControl::toggleFlipped, this, &FrogPilotManageControl::refresh); + } + + void refresh() { + manageButton->setEnabled(params.getBool(key)); + } + + void setManageVisibility(bool visible) { + manageButton->setVisible(visible); + } + + void showEvent(QShowEvent *event) override { + refresh(); + ParamControl::showEvent(event); + } + +signals: + void manageButtonClicked(); + +private: + std::string key; + + ButtonControl *manageButton; + + Params params; +}; + +class FrogPilotParamValueControl : public AbstractControl { + Q_OBJECT +public: + FrogPilotParamValueControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + float min_value, float max_value, const QString &label, const std::map &value_labels = {}, + float interval = 1.0f, bool fast_increase = false, int label_width = 350) + : AbstractControl(title, desc, icon), + fast_increase(fast_increase), interval(interval), label(label), min_value(min_value), max_value(max_value), value_labels(value_labels) { + factor = std::pow(10, std::ceil(-std::log10(interval))); + key = param.toStdString(); + key_type = params.getKeyType(key); + + setupButton(decrement_button, "-"); + setupButton(increment_button, "+"); + + value_label = new QLabel(this); + value_label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + value_label->setFixedSize(QSize(label_width, 100)); + value_label->setStyleSheet("QLabel {color: #E0E879;}"); + + hlayout->addWidget(value_label); + hlayout->addWidget(&decrement_button); + hlayout->addWidget(&increment_button); + + QObject::connect(&decrement_button, &QPushButton::pressed, this, &FrogPilotParamValueControl::decrementPressed); + QObject::connect(&increment_button, &QPushButton::pressed, this, &FrogPilotParamValueControl::incrementPressed); + + last_action_timer.start(); + } + + void decrementPressed() { + if (display_warning && !warning_shown) { + showWarning(); + } + + if (last_action_timer.isValid() && last_action_timer.elapsed() > decrement_button.autoRepeatInterval() + 50) { + decrement_repeating = false; + } + + float delta = decrement_repeating && fast_increase ? interval * 5 : interval; + value = std::max(value - delta, min_value); + + updateValue(); + + if (std::lround(value / interval) % 5 == 0) { + decrement_repeating = true; + } + + last_action_timer.restart(); + } + + void hideEvent(QHideEvent *event) override { + AbstractControl::hideEvent(event); + + warning_shown = false; + + updateParam(); + } + + void incrementPressed() { + if (display_warning && !warning_shown) { + showWarning(); + } + + if (last_action_timer.isValid() && last_action_timer.elapsed() > increment_button.autoRepeatInterval() + 50) { + increment_repeating = false; + } + + float delta = increment_repeating && fast_increase ? interval * 5 : interval; + value = std::min(value + delta, max_value); + + updateValue(); + + if (std::lround(value / interval) % 5 == 0) { + increment_repeating = true; + } + + last_action_timer.restart(); + } + + void refresh() { + if (key_type == ParamKeyType::INT) { + value = std::clamp(std::round(params.getInt(key) * factor) / factor, min_value, max_value); + } else { + value = std::clamp(std::round(params.getFloat(key) * factor) / factor, min_value, max_value); + } + previous_value = value; + + updateDisplay(); + updateParam(); + } + + void setWarning(const QString &newWarning) { + display_warning = true; + + warning = newWarning; + } + + void setupButton(QPushButton &button, const QString &text) { + button.setAutoRepeat(true); + button.setAutoRepeatDelay(500); + button.setAutoRepeatInterval(150); + button.setFixedSize(150, 100); + button.setStyleSheet(buttonStyle); + if (text == "+" || text == "-") { + button.setStyleSheet(button.styleSheet() + " QPushButton { font-size: 50px; }"); + } + button.setText(text); + } + + void showEvent(QShowEvent *event) override { + refresh(); + } + + void showWarning() { + ConfirmationDialog::alert(warning, this); + + warning_shown = true; + } + + void updateControl(const float &newMinValue, const float &newMaxValue, const std::map &newValueLabels = {}) { + min_value = newMinValue; + max_value = newMaxValue; + + value_labels = newValueLabels; + + refresh(); + } + + void updateDisplay() { + QString displayText = QString::number(value) + label; + + for (const std::pair &entry : value_labels) { + if (std::lround(entry.first * factor) == std::lround(value * factor)) { + displayText = entry.second; + break; + } + } + + decrement_button.setEnabled(value > min_value); + increment_button.setEnabled(value < max_value); + + value_label->setText(displayText); + } + + void updateParam() { + if (value == previous_value) { + return; + } + + if (key_type == ParamKeyType::INT) { + params.putInt(key, value); + } else { + params.putFloat(key, value); + } + } + + void updateValue() { + value = std::round(value * factor) / factor; + + emit valueChanged(value); + + updateDisplay(); + } + +signals: + void valueChanged(float value); + +protected: + QLabel *value_label; + +private: + bool decrement_repeating; + bool display_warning; + bool fast_increase; + bool increment_repeating; + bool warning_shown; + + float interval; + float factor; + float max_value; + float min_value; + float previous_value; + float value; + + std::map value_labels; + + std::string key; + + ParamKeyType key_type; + + Params params; + + QElapsedTimer last_action_timer; + + QPushButton decrement_button; + QPushButton increment_button; + + QString label; + QString warning; +}; + +class FrogPilotParamValueButtonControl : public FrogPilotParamValueControl { + Q_OBJECT +public: + FrogPilotParamValueButtonControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + float min_value, float max_value, const QString &label, const std::map &value_labels, + float interval, bool fast_increase, const std::vector &button_params, const std::vector &button_texts, + bool left_button = false, bool checkable = true, int minimum_button_width = 225) + : FrogPilotParamValueControl(param, title, desc, icon, min_value, max_value, label, value_labels, interval, fast_increase, 200), + button_params(button_params), checkable(checkable) { + button_group = new QButtonGroup(this); + button_group->setExclusive(false); + for (int i = 0; i < button_texts.size(); i++) { + QPushButton *button = new QPushButton(button_texts[i], this); + button->setCheckable(checkable); + button->setChecked(checkable && params.getBool(button_params[i].toStdString())); + button->setStyleSheet(buttonStyle); + button->setMinimumWidth(minimum_button_width); + if (left_button) { + hlayout->insertWidget(hlayout->indexOf(value_label) - 1, button); + } else { + hlayout->addWidget(button); + } + button_group->addButton(button, i); + } + + QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) { + if (checkable) { + params.putBool(button_params[id].toStdString(), button_group->button(id)->isChecked()); + } + emit buttonClicked(id); + }); + } + + void refresh() { + if (checkable) { + for (int i = 0; i < button_group->buttons().size(); i++) { + QAbstractButton *button = button_group->button(i); + button->setChecked(params.getBool(button_params[i].toStdString())); + } + } + FrogPilotParamValueControl::refresh(); + } + + void showEvent(QShowEvent *event) override { + refresh(); + FrogPilotParamValueControl::showEvent(event); + } + +public: + void setEnabledButtons(int id, bool enable) { + if (QAbstractButton *button = button_group->button(id)) { + button->setEnabled(enable); + } + } + +signals: + void buttonClicked(int id); + +private: + bool checkable; + + std::vector button_params; + + Params params; + + QButtonGroup *button_group; +}; + +class FrogPilotDualParamValueControl : public QFrame { + Q_OBJECT +public: + FrogPilotDualParamValueControl(FrogPilotParamValueControl *control1, FrogPilotParamValueControl *control2, QWidget *parent = nullptr) : QFrame(parent), control1(control1), control2(control2) { + QHBoxLayout *hlayout = new QHBoxLayout(this); + hlayout->addWidget(control1); + hlayout->addWidget(control2); + } + + void updateControl(const float &newMinValue, const float &newMaxValue, const std::map &newValueLabels = {}) { + control1->updateControl(newMinValue, newMaxValue, newValueLabels); + control2->updateControl(newMinValue, newMaxValue, newValueLabels); + } + + void refresh() { + control1->refresh(); + control2->refresh(); + } + +private: + FrogPilotParamValueControl *control1; + FrogPilotParamValueControl *control2; +}; diff --git a/frogpilot/ui/qt/widgets/navigation_functions.cc b/frogpilot/ui/qt/widgets/navigation_functions.cc new file mode 100644 index 000000000..1ef7c8d36 --- /dev/null +++ b/frogpilot/ui/qt/widgets/navigation_functions.cc @@ -0,0 +1,62 @@ +#include +#include +#include + +#include "frogpilot/ui/qt/widgets/navigation_functions.h" + +MapSelectionControl::MapSelectionControl(const QMap &map, bool isCountry) : selectionType(isCountry ? "nations" : "states") { + mapButtons = new QButtonGroup(this); + mapButtons->setExclusive(false); + + QGridLayout *mapLayout = new QGridLayout(this); + + QList keys = map.keys(); + for (int i = 0; i < keys.size(); ++i) { + QPushButton *button = new QPushButton(map[keys[i]], this); + button->setCheckable(true); + button->setProperty("mapKey", keys[i]); + button->setStyleSheet(buttonStyle); + + mapButtons->addButton(button, i); + + mapLayout->addWidget(button, i / 3, i % 3); + + QObject::connect(button, &QPushButton::toggled, this, &MapSelectionControl::updateSelectedMaps); + } + + loadSelectedMaps(); +} + +void MapSelectionControl::loadSelectedMaps() { + QJsonObject existingMaps = QJsonDocument::fromJson(QByteArray::fromStdString(params.get("MapsSelected"))).object(); + + QSet selectedMaps; + for (const QJsonValue &value : existingMaps.value(selectionType).toArray()) { + selectedMaps.insert(value.toString()); + } + + for (QAbstractButton *button : mapButtons->buttons()) { + button->setChecked(selectedMaps.contains(button->property("mapKey").toString())); + } +} + +void MapSelectionControl::updateSelectedMaps() { + QJsonObject existingMaps = QJsonDocument::fromJson(QByteArray::fromStdString(params.get("MapsSelected"))).object(); + + QSet selectedMaps; + for (const QJsonValue &value : existingMaps.value(selectionType).toArray()) { + selectedMaps.insert(value.toString()); + } + + for (QAbstractButton *button : mapButtons->buttons()) { + QString mapKey = button->property("mapKey").toString(); + if (button->isChecked()) { + selectedMaps.insert(mapKey); + } else { + selectedMaps.remove(mapKey); + } + } + + existingMaps[selectionType] = QJsonArray::fromStringList(selectedMaps.values()); + params.putNonBlocking("MapsSelected", QJsonDocument(existingMaps).toJson(QJsonDocument::Compact).toStdString()); +} diff --git a/frogpilot/ui/qt/widgets/navigation_functions.h b/frogpilot/ui/qt/widgets/navigation_functions.h new file mode 100644 index 000000000..ccd28f514 --- /dev/null +++ b/frogpilot/ui/qt/widgets/navigation_functions.h @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +#include + +#include "frogpilot/ui/qt/widgets/frogpilot_controls.h" + +inline QMap midwestMap = { + {"IL", "Illinois"}, {"IN", "Indiana"}, {"IA", "Iowa"}, + {"KS", "Kansas"}, {"MI", "Michigan"}, {"MN", "Minnesota"}, + {"MO", "Missouri"}, {"NE", "Nebraska"}, {"ND", "North Dakota"}, + {"OH", "Ohio"}, {"SD", "South Dakota"}, {"WI", "Wisconsin"} +}; + +inline QMap northeastMap = { + {"CT", "Connecticut"}, {"ME", "Maine"}, {"MA", "Massachusetts"}, + {"NH", "New Hampshire"}, {"NJ", "New Jersey"}, {"NY", "New York"}, + {"PA", "Pennsylvania"}, {"RI", "Rhode Island"}, {"VT", "Vermont"} +}; + +inline QMap southMap = { + {"AL", "Alabama"}, {"AR", "Arkansas"}, {"DE", "Delaware"}, + {"DC", "District of Columbia"}, {"FL", "Florida"}, {"GA", "Georgia"}, + {"KY", "Kentucky"}, {"LA", "Louisiana"}, {"MD", "Maryland"}, + {"MS", "Mississippi"}, {"NC", "North Carolina"}, {"OK", "Oklahoma"}, + {"SC", "South Carolina"}, {"TN", "Tennessee"}, {"TX", "Texas"}, + {"VA", "Virginia"}, {"WV", "West Virginia"} +}; + +inline QMap westMap = { + {"AK", "Alaska"}, {"AZ", "Arizona"}, {"CA", "California"}, + {"CO", "Colorado"}, {"HI", "Hawaii"}, {"ID", "Idaho"}, + {"MT", "Montana"}, {"NV", "Nevada"}, {"NM", "New Mexico"}, + {"OR", "Oregon"}, {"UT", "Utah"}, {"WA", "Washington"}, + {"WY", "Wyoming"} +}; + +inline QMap territoriesMap = { + {"AS", "American Samoa"}, {"GU", "Guam"}, {"MP", "Northern Mariana Islands"}, + {"PR", "Puerto Rico"}, {"VI", "Virgin Islands"} +}; + +inline QMap africaMap = { + {"DZ", "Algeria"}, {"AO", "Angola"}, {"BJ", "Benin"}, + {"BW", "Botswana"}, {"BF", "Burkina Faso"}, {"BI", "Burundi"}, + {"CM", "Cameroon"}, {"CF", "Central African Republic"}, {"TD", "Chad"}, + {"KM", "Comoros"}, {"CG", "Congo (Brazzaville)"}, {"CD", "Congo (Kinshasa)"}, + {"DJ", "Djibouti"}, {"EG", "Egypt"}, {"GQ", "Equatorial Guinea"}, + {"ER", "Eritrea"}, {"ET", "Ethiopia"}, {"GA", "Gabon"}, + {"GM", "Gambia"}, {"GH", "Ghana"}, {"GN", "Guinea"}, + {"GW", "Guinea-Bissau"}, {"CI", "Ivory Coast"}, {"KE", "Kenya"}, + {"LS", "Lesotho"}, {"LR", "Liberia"}, {"LY", "Libya"}, + {"MG", "Madagascar"}, {"MW", "Malawi"}, {"ML", "Mali"}, + {"MR", "Mauritania"}, {"MA", "Morocco"}, {"MZ", "Mozambique"}, + {"NA", "Namibia"}, {"NE", "Niger"}, {"NG", "Nigeria"}, + {"RW", "Rwanda"}, {"SN", "Senegal"}, {"SL", "Sierra Leone"}, + {"SO", "Somalia"}, {"ZA", "South Africa"}, {"SS", "South Sudan"}, + {"SD", "Sudan"}, {"SZ", "Swaziland"}, {"TZ", "Tanzania"}, + {"TG", "Togo"}, {"TN", "Tunisia"}, {"UG", "Uganda"}, + {"ZM", "Zambia"}, {"ZW", "Zimbabwe"} +}; + +inline QMap antarcticaMap = { + {"AQ", "Antarctica"} +}; + +inline QMap asiaMap = { + {"AF", "Afghanistan"}, {"AM", "Armenia"}, {"AZ", "Azerbaijan"}, + {"BH", "Bahrain"}, {"BD", "Bangladesh"}, {"BT", "Bhutan"}, + {"BN", "Brunei"}, {"KH", "Cambodia"}, {"CN", "China"}, + {"CY", "Cyprus"}, {"TL", "East Timor"}, {"HK", "Hong Kong"}, + {"IN", "India"}, {"ID", "Indonesia"}, {"IR", "Iran"}, + {"IQ", "Iraq"}, {"IL", "Israel"}, {"JP", "Japan"}, + {"JO", "Jordan"}, {"KZ", "Kazakhstan"}, {"KW", "Kuwait"}, + {"KG", "Kyrgyzstan"}, {"LA", "Laos"}, {"LB", "Lebanon"}, + {"MY", "Malaysia"}, {"MV", "Maldives"}, {"MO", "Macao"}, + {"MN", "Mongolia"}, {"MM", "Myanmar"}, {"NP", "Nepal"}, + {"KP", "North Korea"}, {"OM", "Oman"}, {"PK", "Pakistan"}, + {"PS", "Palestine"}, {"PH", "Philippines"}, {"QA", "Qatar"}, + {"RU", "Russia"}, {"SA", "Saudi Arabia"}, {"SG", "Singapore"}, + {"KR", "South Korea"}, {"LK", "Sri Lanka"}, {"SY", "Syria"}, + {"TW", "Taiwan"}, {"TJ", "Tajikistan"}, {"TH", "Thailand"}, + {"TR", "Turkey"}, {"TM", "Turkmenistan"}, {"AE", "United Arab Emirates"}, + {"UZ", "Uzbekistan"}, {"VN", "Vietnam"}, {"YE", "Yemen"} +}; + +inline QMap europeMap = { + {"AL", "Albania"}, {"AT", "Austria"}, {"BY", "Belarus"}, + {"BE", "Belgium"}, {"BA", "Bosnia and Herzegovina"}, {"BG", "Bulgaria"}, + {"HR", "Croatia"}, {"CZ", "Czech Republic"}, {"DK", "Denmark"}, + {"EE", "Estonia"}, {"FI", "Finland"}, {"FR", "France"}, + {"GE", "Georgia"}, {"DE", "Germany"}, {"GR", "Greece"}, + {"HU", "Hungary"}, {"IS", "Iceland"}, {"IE", "Ireland"}, + {"IT", "Italy"}, {"KZ", "Kazakhstan"}, {"LV", "Latvia"}, + {"LT", "Lithuania"}, {"LU", "Luxembourg"}, {"MK", "Macedonia"}, + {"MD", "Moldova"}, {"ME", "Montenegro"}, {"NL", "Netherlands"}, + {"NO", "Norway"}, {"PL", "Poland"}, {"PT", "Portugal"}, + {"RO", "Romania"}, {"RS", "Serbia"}, {"SK", "Slovakia"}, + {"SI", "Slovenia"}, {"ES", "Spain"}, {"SE", "Sweden"}, + {"CH", "Switzerland"}, {"TR", "Turkey"}, {"UA", "Ukraine"}, + {"GB", "United Kingdom"} +}; + +inline QMap northAmericaMap = { + {"BS", "Bahamas"}, {"BZ", "Belize"}, {"CA", "Canada"}, + {"CR", "Costa Rica"}, {"CU", "Cuba"}, {"DO", "Dominican Republic"}, + {"SV", "El Salvador"}, {"GL", "Greenland"}, {"GD", "Grenada"}, + {"GT", "Guatemala"}, {"HT", "Haiti"}, {"HN", "Honduras"}, + {"JM", "Jamaica"}, {"MX", "Mexico"}, {"NI", "Nicaragua"}, + {"PA", "Panama"}, {"TT", "Trinidad and Tobago"}, {"US", "United States"} +}; + +inline QMap oceaniaMap = { + {"AU", "Australia"}, {"FJ", "Fiji"}, {"TF", "French Southern Territories"}, + {"NC", "New Caledonia"}, {"NZ", "New Zealand"}, {"PG", "Papua New Guinea"}, + {"SB", "Solomon Islands"}, {"VU", "Vanuatu"} +}; + +inline QMap southAmericaMap = { + {"AR", "Argentina"}, {"BO", "Bolivia"}, {"BR", "Brazil"}, + {"CL", "Chile"}, {"CO", "Colombia"}, {"EC", "Ecuador"}, + {"FK", "Falkland Islands"}, {"GY", "Guyana"}, {"PY", "Paraguay"}, + {"PE", "Peru"}, {"SR", "Suriname"}, {"UY", "Uruguay"}, + {"VE", "Venezuela"} +}; + +inline QString calculateDirectorySize(const QDir &directory) { + constexpr double MB = 1024.0 * 1024.0; + constexpr double GB = 1024.0 * MB; + + if (!directory.exists()) { + return QObject::tr("0 MB"); + } + + double totalSize = 0; + QDirIterator it(directory.absolutePath(), QDir::Files, QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + totalSize += it.fileInfo().size(); + } + + if (totalSize >= GB) { + return QString::number(totalSize / GB, 'f', 2) + QObject::tr(" GB"); + } + return QString::number(totalSize / MB, 'f', 2) + QObject::tr(" MB"); +} + +inline QString daySuffix(int day) { + if (day % 10 == 1 && day != 11) return "st"; + if (day % 10 == 2 && day != 12) return "nd"; + if (day % 10 == 3 && day != 13) return "rd"; + return "th"; +} + +inline QString formatCurrentDate() { + QDate currentDate = QDate::currentDate(); + return currentDate.toString("MMMM d'") + daySuffix(currentDate.day()) + QString(", %1").arg(currentDate.year()); +} + +inline QString formatElapsedTime(float elapsedMilliseconds) { + int totalSeconds = elapsedMilliseconds / 1000; + int hours = totalSeconds / 3600; + int minutes = (totalSeconds % 3600) / 60; + int seconds = totalSeconds % 60; + + QString formattedTime; + if (hours > 0) { + formattedTime += QString::number(hours) + (hours == 1 ? QObject::tr(" hour ") : QObject::tr(" hours ")); + } + if (minutes > 0) { + formattedTime += QString::number(minutes) + (minutes == 1 ? QObject::tr(" minute ") : QObject::tr(" minutes ")); + } + formattedTime += QString::number(seconds) + (seconds == 1 ? QObject::tr(" second") : QObject::tr(" seconds")); + + return formattedTime.trimmed(); +} + +inline QString formatETA(float elapsedTime, int downloadedFiles, int previousDownloadedFiles, int totalFiles, QDateTime &startTime) { + static QDateTime estimatedFinishTime; + + static float previousElapsedTime; + + if (downloadedFiles != previousDownloadedFiles) { + estimatedFinishTime = startTime.addMSecs((elapsedTime * totalFiles) / downloadedFiles); + } else { + estimatedFinishTime = estimatedFinishTime.addSecs((previousElapsedTime - elapsedTime) / 1000); + } + previousElapsedTime = elapsedTime; + + int remainingTime = QDateTime::currentDateTime().secsTo(estimatedFinishTime); + + QString estimatedFinishTimeStr = estimatedFinishTime.toString("h:mm AP"); + QString remainingTimeStr = formatElapsedTime(remainingTime * 1000); + + return QString("%1 (%2)").arg(remainingTimeStr).arg(estimatedFinishTimeStr); +} + +class MapSelectionControl : public QWidget { + Q_OBJECT + +public: + MapSelectionControl(const QMap &map, bool isCountry = false); + +private: + void loadSelectedMaps(); + void updateSelectedMaps(); + + Params params; + + QButtonGroup *mapButtons; + + QString selectionType; +}; diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index d89d991af..415c13594 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -39,8 +39,16 @@ qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/body.cc", "qt/onroad/buttons.cc", "qt/onroad/alerts.cc", "qt/onroad/driver_monitoring.cc", "qt/onroad/hud.cc"] # FrogPilot variables -frogpilot_src = ["../../frogpilot/ui/frogpilot_ui.cc", "../../frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc", - "../../frogpilot/ui/qt/onroad/frogpilot_buttons.cc", "../../frogpilot/ui/qt/onroad/frogpilot_onroad.cc"] +frogpilot_src = ["../../frogpilot/ui/frogpilot_ui.cc", "../../frogpilot/ui/qt/offroad/data_settings.cc", + "../../frogpilot/ui/qt/offroad/device_settings.cc", "../../frogpilot/ui/qt/offroad/frogpilot_settings.cc", + "../../frogpilot/ui/qt/offroad/lateral_settings.cc", "../../frogpilot/ui/qt/offroad/longitudinal_settings.cc", + "../../frogpilot/ui/qt/offroad/maps_settings.cc", "../../frogpilot/ui/qt/offroad/model_settings.cc", + "../../frogpilot/ui/qt/offroad/navigation_settings.cc", "../../frogpilot/ui/qt/offroad/sounds_settings.cc", + "../../frogpilot/ui/qt/offroad/theme_settings.cc", "../../frogpilot/ui/qt/offroad/utilities.cc", + "../../frogpilot/ui/qt/offroad/vehicle_settings.cc", "../../frogpilot/ui/qt/offroad/visual_settings.cc", + "../../frogpilot/ui/qt/offroad/wheel_settings.cc", "../../frogpilot/ui/qt/onroad/frogpilot_annotated_camera.cc", + "../../frogpilot/ui/qt/onroad/frogpilot_buttons.cc", "../../frogpilot/ui/qt/onroad/frogpilot_onroad.cc", + "../../frogpilot/ui/qt/widgets/navigation_functions.cc"] qt_src += frogpilot_src # build translation files diff --git a/selfdrive/ui/qt/network/wifi_manager.h b/selfdrive/ui/qt/network/wifi_manager.h index 76baec3bd..a0fdfa4ee 100644 --- a/selfdrive/ui/qt/network/wifi_manager.h +++ b/selfdrive/ui/qt/network/wifi_manager.h @@ -70,6 +70,7 @@ public: QString getTetheringPassword(); // FrogPilot variables + QString getIp4Address(); private: QString adapter; // Path to network manager wifi-device @@ -83,7 +84,6 @@ private: QString getAdapter(const uint = NM_DEVICE_TYPE_WIFI); uint getAdapterType(const QDBusObjectPath &path); - QString getIp4Address(); void deactivateConnectionBySsid(const QString &ssid); void deactivateConnection(const QDBusObjectPath &path); QVector getActiveConnections(); diff --git a/selfdrive/ui/qt/offroad/developer_panel.cc b/selfdrive/ui/qt/offroad/developer_panel.cc index 9370d7cfd..573fbd419 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.cc +++ b/selfdrive/ui/qt/offroad/developer_panel.cc @@ -2,28 +2,39 @@ #include "selfdrive/ui/qt/widgets/ssh_keys.h" #include "selfdrive/ui/qt/widgets/controls.h" -DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) { +DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : QFrame(parent) { + mainLayout = new QStackedLayout(this); + + mainWidget = new QWidget(this); + QVBoxLayout *mainListLayout = new QVBoxLayout(mainWidget); + + mainListLayout->setContentsMargins(50, 25, 50, 25); + mainListLayout->setSpacing(20); + + FrogPilotListWidget *mainList = new FrogPilotListWidget(mainWidget); + mainListLayout->addWidget(mainList); + adbToggle = new ParamControl("AdbEnabled", tr("Enable ADB"), tr("ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. See https://docs.comma.ai/how-to/connect-to-comma for more info."), ""); - addItem(adbToggle); + mainList->addItem(adbToggle); // SSH keys - addItem(new SshToggle()); - addItem(new SshControl()); + mainList->addItem(new SshToggle()); + mainList->addItem(new SshControl()); joystickToggle = new ParamControl("JoystickDebugMode", tr("Joystick Debug Mode"), "", ""); QObject::connect(joystickToggle, &ParamControl::toggleFlipped, [=](bool state) { params.putBool("LongitudinalManeuverMode", false); longManeuverToggle->refresh(); }); - addItem(joystickToggle); + mainList->addItem(joystickToggle); longManeuverToggle = new ParamControl("LongitudinalManeuverMode", tr("Longitudinal Maneuver Mode"), "", ""); QObject::connect(longManeuverToggle, &ParamControl::toggleFlipped, [=](bool state) { params.putBool("JoystickDebugMode", false); joystickToggle->refresh(); }); - addItem(longManeuverToggle); + mainList->addItem(longManeuverToggle); experimentalLongitudinalToggle = new ParamControl( "AlphaLongitudinalEnabled", @@ -38,7 +49,7 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) { QObject::connect(experimentalLongitudinalToggle, &ParamControl::toggleFlipped, [=]() { updateToggles(offroad); }); - addItem(experimentalLongitudinalToggle); + mainList->addItem(experimentalLongitudinalToggle); // Joystick and longitudinal maneuvers should be hidden on release branches is_release = params.getBool("IsReleaseBranch"); @@ -47,6 +58,201 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) { QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanel::updateToggles); // FrogPilot variables + QJsonObject shownDescriptions = QJsonDocument::fromJson(QString::fromStdString(params.get("ShownToggleDescriptions")).toUtf8()).object(); + QString className = this->metaObject()->className(); + + if (!shownDescriptions.value(className).toBool(false)) { + forceOpenDescriptions = true; + } + + std::vector keys = params.allKeys(); + for (const std::string &key : keys) { + frogpilotToggleLevels[QString::fromStdString(key)] = params.getTuningLevel(key); + } + + developerUIToggle = new FrogPilotManageControl("DeveloperUI", tr("Developer UI"), tr("Detailed information about openpilot's internal operations."), ""); + QObject::connect(developerUIToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { + mainLayout->setCurrentWidget(developerUIPanel); + emit openSubPanel(); + }); + mainList->insertItem(0, developerUIToggle); + + mainLayout->addWidget(mainWidget); + + FrogPilotListWidget *developerUIList = new FrogPilotListWidget(this); + developerUIList->setContentsMargins(50, 25, 50, 25); + developerUIPanel = new ScrollView(developerUIList, this); + mainLayout->addWidget(developerUIPanel); + + FrogPilotListWidget *developerMetricList = new FrogPilotListWidget(this); + developerMetricList->setContentsMargins(50, 25, 50, 25); + developerMetricPanel = new ScrollView(developerMetricList, this); + mainLayout->addWidget(developerMetricPanel); + + FrogPilotListWidget *developerSidebarList = new FrogPilotListWidget(this); + developerSidebarList->setContentsMargins(50, 25, 50, 25); + developerSidebarPanel = new ScrollView(developerSidebarList, this); + mainLayout->addWidget(developerSidebarPanel); + + FrogPilotListWidget *developerWidgetList = new FrogPilotListWidget(this); + developerWidgetList->setContentsMargins(50, 25, 50, 25); + developerWidgetPanel = new ScrollView(developerWidgetList, this); + mainLayout->addWidget(developerWidgetPanel); + + std::vector> developerToggles { + {"DeveloperMetrics", tr("Developer Metrics"), tr("Performance data, sensor readings, and system metrics for debugging and optimizing openpilot."), ""}, + {"AdjacentPathMetrics", tr("Adjacent Lane Metrics"), tr("Show the width of the adjacent lanes."), ""}, + {"BorderMetrics", tr("Border Metrics"), tr("Show statuses along the border of the driving screen.

Blind Spot: The border turns red when a vehicle is in a blind spot
Steering Torque: The border goes from green to red according to how much steering torque is being used
Turn Signal: The border flashes yellow when a turn signal is on"), ""}, + {"FPSCounter", tr("FPS Display"), tr("Show the frames per second (FPS) at the bottom of the driving screen."), ""}, + {"LeadInfo", tr("Lead Info"), tr("Show each tracked vehicle's distance and speed below its marker."), ""}, + {"NumericalTemp", tr("Numerical Temperature Gauge"), tr("Show a numerical temperature in the sidebar instead of the status labels."), ""}, + {"SidebarMetrics", tr("Sidebar Metrics"), tr("Display system information (CPU, GPU, RAM usage, IP address, device storage) in the sidebar."), ""}, + {"UseSI", tr("Use International System of Units"), tr("Display measurements using the \"International System of Units\" (SI) standard."), ""}, + + {"DeveloperSidebar", tr("Developer Sidebar"), tr("Display debugging info and metrics in a dedicated sidebar on the right side of the screen."), ""}, + {"DeveloperSidebarMetric1", tr("Metric #1"), tr("Select the metric shown in the first \"Developer Sidebar\" widget."), ""}, + {"DeveloperSidebarMetric2", tr("Metric #2"), tr("Select the metric shown in the second \"Developer Sidebar\" widget."), ""}, + {"DeveloperSidebarMetric3", tr("Metric #3"), tr("Select the metric shown in the third \"Developer Sidebar\" widget."), ""}, + {"DeveloperSidebarMetric4", tr("Metric #4"), tr("Select the metric shown in the fourth \"Developer Sidebar\" widget."), ""}, + {"DeveloperSidebarMetric5", tr("Metric #5"), tr("Select the metric shown in the fifth \"Developer Sidebar\" widget."), ""}, + {"DeveloperSidebarMetric6", tr("Metric #6"), tr("Select the metric shown in the sixth \"Developer Sidebar\" widget."), ""}, + {"DeveloperSidebarMetric7", tr("Metric #7"), tr("Select the metric shown in the seventh \"Developer Sidebar\" widget."), ""}, + + {"DeveloperWidgets", tr("Developer Widgets"), tr("Overlays for debugging visuals, internal states, and model predictions on the driving screen."), ""}, + {"AdjacentLeadsUI", tr("Adjacent Leads Tracking"), tr("Display adjacent leads detected by the car's radar to the left and right of the current driving path."), ""}, + {"ShowStoppingPoint", tr("Model Stopping Point"), tr("Show a stop-sign marker where the model intends to stop."), ""}, + {"RadarTracksUI", tr("Radar Tracks"), tr("Display all radar points produced by the car's radar."), ""}, + }; + + for (const auto &[param, title, desc, icon] : developerToggles) { + AbstractControl *developerToggle; + + if (param == "DeveloperMetrics") { + FrogPilotManageControl *developerMetricsToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(developerMetricsToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { + mainLayout->setCurrentWidget(developerMetricPanel); + emit openSubSubPanel(); + }); + developerToggle = developerMetricsToggle; + } else if (param == "BorderMetrics") { + std::vector borderToggles{"BlindSpotMetrics", "ShowSteering", "SignalMetrics"}; + std::vector borderToggleNames{tr("Blind Spot"), tr("Steering Torque"), tr("Turn Signal")}; + borderMetricsButton = new FrogPilotButtonToggleControl(param, title, desc, icon, borderToggles, borderToggleNames); + developerToggle = borderMetricsButton; + } else if (param == "NumericalTemp") { + std::vector temperatureToggles{"Fahrenheit"}; + std::vector temperatureToggleNames{tr("Fahrenheit")}; + developerToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, temperatureToggles, temperatureToggleNames); + } else if (param == "SidebarMetrics") { + sidebarMetricsToggles = {"ShowCPU", "ShowGPU", "ShowIP", "ShowMemoryUsage", "ShowStorageLeft", "ShowStorageUsed"}; + std::vector sidebarMetricsToggleNames{tr("CPU"), tr("GPU"), tr("IP"), tr("RAM"), tr("SSD Left"), tr("SSD Used")}; + sidebarMetricsToggle = new FrogPilotButtonsControl(title, desc, icon, sidebarMetricsToggleNames, true, false, 150); + for (int i = 0; i < sidebarMetricsToggles.size(); ++i) { + if (params.getBool(sidebarMetricsToggles[i].toStdString())) { + sidebarMetricsToggle->setCheckedButton(i); + } + } + QObject::connect(sidebarMetricsToggle, &FrogPilotButtonsControl::buttonClicked, [this](int id) { + params.putBool(sidebarMetricsToggles[id].toStdString(), !params.getBool(sidebarMetricsToggles[id].toStdString())); + + if (id == 0) { + params.putBool("ShowGPU", false); + } else if (id == 1) { + params.putBool("ShowCPU", false); + } else if (id == 3) { + params.putBool("ShowStorageLeft", false); + params.putBool("ShowStorageUsed", false); + } else if (id == 4) { + params.putBool("ShowMemoryUsage", false); + params.putBool("ShowStorageUsed", false); + } else if (id == 5) { + params.putBool("ShowMemoryUsage", false); + params.putBool("ShowStorageLeft", false); + } + + sidebarMetricsToggle->clearCheckedButtons(); + for (int i = 0; i < sidebarMetricsToggles.size(); ++i) { + if (params.getBool(sidebarMetricsToggles[i].toStdString())) { + sidebarMetricsToggle->setCheckedButton(i); + } + } + }); + developerToggle = sidebarMetricsToggle; + } else if (param == "DeveloperSidebar") { + FrogPilotManageControl *developerSidebarToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(developerSidebarToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { + mainLayout->setCurrentWidget(developerSidebarPanel); + emit openSubSubPanel(); + }); + developerToggle = developerSidebarToggle; + } else if (developerSidebarKeys.contains(param)) { + QMap developerSidebarMetricOptions { + {0, tr("None")}, + {1, tr("Acceleration: Current")}, + {2, tr("Acceleration: Max")}, + {3, tr("Auto Tune: Actuator Delay")}, + {4, tr("Auto Tune: Friction")}, + {5, tr("Auto Tune: Lateral Acceleration")}, + {6, tr("Auto Tune: Steer Ratio")}, + {7, tr("Auto Tune: Stiffness Factor")}, + {8, tr("Engagement %: Lateral")}, + {9, tr("Engagement %: Longitudinal")}, + {10, tr("Lateral Control: Steering Angle")}, + {11, tr("Lateral Control: Torque % Used")}, + {12, tr("Longitudinal Control: Actuator Acceleration Output")}, + {13, tr("Longitudinal MPC: Danger Factor")}, + {14, tr("Longitudinal MPC Jerk: Acceleration")}, + {15, tr("Longitudinal MPC Jerk: Danger Zone")}, + {16, tr("Longitudinal MPC Jerk: Speed Control")}, + }; + + ButtonControl *metricToggle = new ButtonControl(title, tr("SELECT"), desc); + QObject::connect(metricToggle, &ButtonControl::clicked, [metricToggle, key = param, developerSidebarMetricOptions, this]() mutable { + QString current = developerSidebarMetricOptions.value(params.getInt(key.toStdString()), tr("None")); + QString selection = MultiOptionDialog::getSelection(tr("Select a metric to display"), developerSidebarMetricOptions.values(), current, this); + + if (!selection.isEmpty()) { + int selectedMetric = developerSidebarMetricOptions.key(selection); + + params.putInt(key.toStdString(), selectedMetric); + + metricToggle->setValue(selection); + } + }); + metricToggle->setValue(developerSidebarMetricOptions.value(params.getInt(param.toStdString()), tr("None"))); + developerToggle = metricToggle; + } else if (param == "DeveloperWidgets") { + FrogPilotManageControl *developerWidgetsToggle = new FrogPilotManageControl(param, title, desc, icon); + QObject::connect(developerWidgetsToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { + mainLayout->setCurrentWidget(developerWidgetPanel); + emit openSubSubPanel(); + }); + developerToggle = developerWidgetsToggle; + } else if (param == "ShowStoppingPoint") { + std::vector stoppingPointToggles{"ShowStoppingPointMetrics"}; + std::vector stoppingPointToggleNames{tr("Show Distance")}; + developerToggle = new FrogPilotButtonToggleControl(param, title, desc, icon, stoppingPointToggles, stoppingPointToggleNames); + } else { + developerToggle = new ParamControl(param, title, desc, icon); + } + + toggles[param] = developerToggle; + + if (developerMetricKeys.contains(param)) { + developerMetricList->addItem(developerToggle); + } else if (developerSidebarKeys.contains(param)) { + developerSidebarList->addItem(developerToggle); + } else if (developerWidgetKeys.contains(param)) { + developerWidgetList->addItem(developerToggle); + } else { + developerUIList->addItem(developerToggle); + + parentKeys.insert(param); + } + } + + QObject::connect(parent, &SettingsWindow::closeSubPanel, [this]() {mainLayout->setCurrentWidget(mainWidget);}); + QObject::connect(parent, &SettingsWindow::closeSubSubPanel, [this]() {mainLayout->setCurrentWidget(developerUIPanel);}); } void DeveloperPanel::updateToggles(bool _offroad) { @@ -63,6 +269,15 @@ void DeveloperPanel::updateToggles(bool _offroad) { } // FrogPilot variables + for (auto &[key, toggle] : toggles) { + if (toggle == btn) { + btn->setEnabled(true); + break; + } + } + if (developerUIToggle == btn) { + btn->setEnabled(true); + } } // longManeuverToggle and experimentalLongitudinalToggle should not be toggleable if the car does not have longitudinal control @@ -87,6 +302,10 @@ void DeveloperPanel::updateToggles(bool _offroad) { longManeuverToggle->setEnabled(hasLongitudinalControl(CP) && _offroad); // FrogPilot variables + hasOpenpilotLongitudinal = hasLongitudinalControl(CP); + hasRadar = !CP.getRadarUnavailable(); + + borderMetricsButton->setVisibleButton(0, CP.getEnableBsm()); } else { longManeuverToggle->setEnabled(false); experimentalLongitudinalToggle->setVisible(false); @@ -96,10 +315,72 @@ void DeveloperPanel::updateToggles(bool _offroad) { offroad = _offroad; // FrogPilot variables + tuningLevel = params.getInt("TuningLevel"); + + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + toggle->setVisible(false); + } + } + + for (auto &[key, toggle] : toggles) { + if (parentKeys.contains(key)) { + continue; + } + + bool setVisible = tuningLevel >= frogpilotToggleLevels[key].toDouble(); + + if (key == "AdjacentLeadsUI") { + setVisible &= hasRadar && !(params.getBool("AdvancedCustomUI") && params.getBool("HideLeadMarker")); + } + + else if (key == "RadarTracksUI") { + setVisible &= hasRadar; + } + + else if (key == "ShowStoppingPoint") { + setVisible &= hasOpenpilotLongitudinal; + } + + toggle->setVisible(setVisible); + + if (setVisible) { + if (developerMetricKeys.contains(key)) { + toggles["DeveloperMetrics"]->setVisible(true); + } else if (developerSidebarKeys.contains(key)) { + toggles["DeveloperSidebar"]->setVisible(true); + } else if (developerUIKeys.contains(key)) { + toggles["DeveloperUI"]->setVisible(true); + } else if (developerWidgetKeys.contains(key)) { + toggles["DeveloperWidgets"]->setVisible(true); + } + } + } + + borderMetricsButton->setVisibleButton(0, hasBSM); + + developerUIToggle->setVisible(tuningLevel >= frogpilotToggleLevels["DeveloperUI"].toDouble()); + + openDescriptions(forceOpenDescriptions, toggles); + + update(); } void DeveloperPanel::showEvent(QShowEvent *event) { updateToggles(offroad); // FrogPilot variables + for (int i = 0; i < sidebarMetricsToggles.size(); ++i) { + if (params.getBool(sidebarMetricsToggles[i].toStdString())) { + sidebarMetricsToggle->setCheckedButton(i); + } + } + + QJsonObject shownDescriptions = QJsonDocument::fromJson(QString::fromStdString(params.get("ShownToggleDescriptions")).toUtf8()).object(); + QString className = this->metaObject()->className(); + + if (!shownDescriptions.value(className).toBool(false)) { + shownDescriptions.insert(className, true); + params.put("ShownToggleDescriptions", QJsonDocument(shownDescriptions).toJson(QJsonDocument::Compact).toStdString()); + } } diff --git a/selfdrive/ui/qt/offroad/developer_panel.h b/selfdrive/ui/qt/offroad/developer_panel.h index e707cffd4..be202203e 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.h +++ b/selfdrive/ui/qt/offroad/developer_panel.h @@ -2,13 +2,18 @@ #include "selfdrive/ui/qt/offroad/settings.h" -class DeveloperPanel : public ListWidget { +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + +class DeveloperPanel : public QFrame { Q_OBJECT public: explicit DeveloperPanel(SettingsWindow *parent); void showEvent(QShowEvent *event) override; // FrogPilot variables +signals: + void openSubPanel(); + void openSubSubPanel(); private: Params params; @@ -20,6 +25,40 @@ private: bool offroad = false; // FrogPilot variables + bool forceOpenDescriptions = false; + bool hasBSM = true; + bool hasOpenpilotLongitudinal = true; + bool hasRadar = true; + + int tuningLevel; + + std::map toggles; + + std::vector sidebarMetricsToggles; + + FrogPilotButtonsControl *sidebarMetricsToggle; + + FrogPilotButtonToggleControl *borderMetricsButton; + + FrogPilotManageControl *developerUIToggle; + + QJsonObject frogpilotToggleLevels; + + QSet developerMetricKeys = {"AdjacentPathMetrics", "BorderMetrics", "FPSCounter", "LeadInfo", "NumericalTemp", "SidebarMetrics", "UseSI"}; + QSet developerSidebarKeys = {"DeveloperSidebarMetric1", "DeveloperSidebarMetric2", "DeveloperSidebarMetric3", "DeveloperSidebarMetric4", "DeveloperSidebarMetric5", "DeveloperSidebarMetric6", "DeveloperSidebarMetric7"}; + QSet developerUIKeys = {"DeveloperMetrics", "DeveloperSidebar", "DeveloperWidgets"}; + QSet developerWidgetKeys = {"AdjacentLeadsUI", "RadarTracksUI", "ShowStoppingPoint"}; + + QSet parentKeys; + + QStackedLayout *mainLayout; + + QWidget *mainWidget; + + ScrollView *developerMetricPanel; + ScrollView *developerSidebarPanel; + ScrollView *developerUIPanel; + ScrollView *developerWidgetPanel; private slots: void updateToggles(bool _offroad); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index e676456fb..63556cfde 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -16,6 +16,8 @@ #include "selfdrive/ui/qt/offroad/developer_panel.h" #include "selfdrive/ui/qt/offroad/firehose.h" +#include "frogpilot/ui/qt/offroad/frogpilot_settings.h" + TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { // param, title, desc, icon, restart needed std::vector> toggle_defs{ @@ -121,6 +123,9 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { toggles["ExperimentalMode"]->setConfirmation(true, true); // FrogPilot variables + connect(toggles["IsMetric"], &ToggleControl::toggleFlipped, [=](bool isMetric) { + updateMetric(isMetric); + }); } void TogglesPanel::updateState(const UIState &s) { @@ -414,6 +419,13 @@ void SettingsWindow::showEvent(QShowEvent *event) { // FrogPilot variables void SettingsWindow::hideEvent(QHideEvent *event) { + closePanel(); + closeSubPanel(); + + panelOpen = false; + subPanelOpen = false; + subSubPanelOpen = false; + subSubSubPanelOpen = false; } void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { @@ -466,7 +478,21 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter); QObject::connect(close_btn, &QPushButton::clicked, [this]() { // FrogPilot variables - closeSettings(); + if (subSubSubPanelOpen) { + closeSubSubSubPanel(); + subSubSubPanelOpen = false; + } else if (subSubPanelOpen) { + closeSubSubPanel(); + subSubPanelOpen = false; + } else if (subPanelOpen) { + closeSubPanel(); + subPanelOpen = false; + } else if (panelOpen) { + closePanel(); + panelOpen = false; + } else { + closeSettings(); + } }); // setup panels @@ -482,14 +508,26 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QObject::connect(uiState()->prime_state, &PrimeState::changed, networking, &Networking::setPrimeType); // FrogPilot variables + QObject::connect(toggles, &TogglesPanel::updateMetric, this, &SettingsWindow::updateMetric); + + FrogPilotSettingsWindow *frogpilotSettingsWindow = new FrogPilotSettingsWindow(this); + QObject::connect(frogpilotSettingsWindow, &FrogPilotSettingsWindow::openPanel, [this]() {panelOpen=true;}); + QObject::connect(frogpilotSettingsWindow, &FrogPilotSettingsWindow::openSubPanel, [this]() {subPanelOpen=true;}); + QObject::connect(frogpilotSettingsWindow, &FrogPilotSettingsWindow::openSubSubPanel, [this]() {subSubPanelOpen=true;}); + QObject::connect(frogpilotSettingsWindow, &FrogPilotSettingsWindow::openSubSubSubPanel, [this]() {subSubSubPanelOpen=true;}); + QObject::connect(frogpilotSettingsWindow, &FrogPilotSettingsWindow::tuningLevelChanged, this, &SettingsWindow::updateDeveloperToggle); + + DeveloperPanel *developerPanel = new DeveloperPanel(this); + QObject::connect(developerPanel, &DeveloperPanel::openSubPanel, [this]() {subPanelOpen=true;}); + QObject::connect(developerPanel, &DeveloperPanel::openSubSubPanel, [this]() {subSubPanelOpen=true;}); QList> panels = { {tr("Device"), device}, {tr("Network"), networking}, {tr("Toggles"), toggles}, {tr("Software"), new SoftwarePanel(this)}, - {tr("Firehose"), new FirehosePanel(this)}, - {tr("Developer"), new DeveloperPanel(this)}, + {tr("Developer"), developerPanel}, + {tr("FrogPilot"), frogpilotSettingsWindow}, }; nav_btns = new QButtonGroup(this); @@ -524,7 +562,68 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QObject::connect(btn, &QPushButton::clicked, [=, w = panel_frame]() { // FrogPilot variables + if (w->widget() == frogpilotSettingsWindow) { + bool tuningLevelConfirmed = params.getBool("TuningLevelConfirmed"); + if (!tuningLevelConfirmed) { + int frogpilotHours = QJsonDocument::fromJson(QString::fromStdString(params.get("FrogPilotStats")).toUtf8()).object().value("FrogPilotSeconds").toInt() / (60 * 60); + int openpilotHours = params.getInt("openpilotMinutes") / 60; + + if (frogpilotHours < 1 && openpilotHours < 100) { + if (openpilotHours < 10) { + if (ConfirmationDialog::alert(tr("Welcome to FrogPilot! Since you're new to openpilot, the \"Minimal\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) { + params.putBool("TuningLevelConfirmed", true); + params.putInt("TuningLevel", 0); + } + } else { + if (ConfirmationDialog::alert(tr("Welcome to FrogPilot! Since you're new to FrogPilot, the \"Minimal\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) { + params.putBool("TuningLevelConfirmed", true); + params.putInt("TuningLevel", 0); + } + } + } else if (frogpilotHours < 50 && openpilotHours < 100) { + if (ConfirmationDialog::alert(tr("Since you're fairly new to FrogPilot, the \"Minimal\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) { + params.putBool("TuningLevelConfirmed", true); + params.putInt("TuningLevel", 0); + } + } else if (frogpilotHours < 100) { + if (openpilotHours >= 100) { + if (ConfirmationDialog::alert(tr("Since you're experienced with openpilot, the \"Standard\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) { + params.putBool("TuningLevelConfirmed", true); + params.putInt("TuningLevel", 1); + } + } else { + if (ConfirmationDialog::alert(tr("Since you're experienced with FrogPilot, the \"Standard\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) { + params.putBool("TuningLevelConfirmed", true); + params.putInt("TuningLevel", 1); + } + } + } else if (frogpilotHours >= 100) { + if (ConfirmationDialog::alert(tr("Since you're very experienced with FrogPilot, the \"Advanced\" toggle preset has been applied, but you can change this at any time via the \"Tuning Level\" button!"), this, true)) { + params.putBool("TuningLevelConfirmed", true); + params.putInt("TuningLevel", 2); + } + } + updateTuningLevel(); + } + } + + if (subSubSubPanelOpen) { + closeSubSubSubPanel(); + subSubSubPanelOpen = false; + } + if (subSubPanelOpen) { + closeSubSubPanel(); + subSubPanelOpen = false; + } + if (subPanelOpen) { + closeSubPanel(); + subPanelOpen = false; + } + if (panelOpen) { + closePanel(); + panelOpen = false; + } btn->setChecked(true); panel_widget->setCurrentWidget(w); }); @@ -551,4 +650,17 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { border-radius: 30px; } )"); + + // FrogPilot variables + updateDeveloperToggle(params.getInt("TuningLevel")); +} + +// FrogPilot variables +void SettingsWindow::updateDeveloperToggle(int tuningLevel) { + for (QAbstractButton *btn : nav_btns->buttons()) { + if (btn->text() == tr("Developer")) { + btn->setVisible(tuningLevel >= 3); + break; + } + } } diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index c5b7a5aa5..e0a1ca15a 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -36,6 +36,12 @@ signals: void scrollToToggle(const QString ¶m); // FrogPilot variables + void closePanel(); + void closeSubPanel(); + void closeSubSubPanel(); + void closeSubSubSubPanel(); + void updateMetric(bool isMetric, bool bootRun=false); + void updateTuningLevel(); private: QPushButton *sidebar_alert_widget; @@ -44,6 +50,13 @@ private: QStackedWidget *panel_widget; // FrogPilot variables + void updateDeveloperToggle(int tuningLevel); + + bool panelOpen; + bool subPanelOpen; + bool subSubPanelOpen; + bool subSubSubPanelOpen; + Params params; }; @@ -75,6 +88,7 @@ public: signals: // FrogPilot variables + void updateMetric(bool metric, bool bootRun=false); public slots: void expandToggleDescription(const QString ¶m); diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 40dda971f..992fe7407 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -49,7 +49,12 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons } if (!description->text().isEmpty()) { - description->setVisible(!description->isVisible()); + if (description->isVisible()) { + emit hideDescriptionEvent(); + description->setVisible(false); + } else { + description->setVisible(true); + } } }); diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 4dc094a23..5ccf5ee22 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -68,6 +68,7 @@ signals: void showDescriptionEvent(); // FrogPilot variables + void hideDescriptionEvent(); protected: AbstractControl(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); @@ -135,6 +136,12 @@ public: } // FrogPilot variables + void forceOn(bool force) { + toggle.setEnabled(!force); + if (force && !toggle.on) { + toggle.togglePosition(); + } + } signals: void toggleFlipped(bool state); diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index cec7c4183..32c6fcefa 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -143,21 +143,27 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s line->backspace(); // FrogPilot variables + updateMaxLengthSublabel(line->text()); }); QObject::connect(k, &Keyboard::emitKey, this, [=](const QString &key) { - line->insert(key.left(1)); + if (line->text().length() < maxLength || maxLength == DEFAULT_MAX_LENGTH) { + line->insert(key.left(1)); + updateMaxLengthSublabel(line->text()); + } }); main_layout->addWidget(k, 2, Qt::AlignBottom); } QString InputDialog::getText(const QString &prompt, QWidget *parent, const QString &subtitle, - bool secret, int minLength, const QString &defaultText) { + bool secret, int minLength, const QString &defaultText, int maxLength) { InputDialog d(prompt, parent, subtitle, secret); d.line->setText(defaultText); d.setMinLength(minLength); // FrogPilot variables + d.setMaxLength(maxLength); + d.updateMaxLengthSublabel(defaultText); const int ret = d.exec(); return ret ? d.text() : QString(); @@ -192,11 +198,20 @@ void InputDialog::setMinLength(int length) { } // FrogPilot variables +void InputDialog::setMaxLength(int length) { + maxLength = length; +} + +void InputDialog::updateMaxLengthSublabel(const QString &text) { + if (maxLength != DEFAULT_MAX_LENGTH) { + sublabel->setText(tr("Characters: %1/%2").arg(text.length()).arg(maxLength)); + } +} // ConfirmationDialog ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text, - const bool rich, QWidget *parent) : DialogBase(parent) { + const bool rich, QWidget *parent, const bool is_long) : DialogBase(parent) { QFrame *container = new QFrame(this); container->setStyleSheet(R"( QFrame { background-color: #1B1B1B; color: #C9C9C9; } @@ -204,7 +219,7 @@ ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString #confirm_btn:pressed { background-color: #3049F4; } )"); QVBoxLayout *main_layout = new QVBoxLayout(container); - main_layout->setContentsMargins(32, rich ? 32 : 120, 32, 32); + main_layout->setContentsMargins(32, rich || is_long ? 32 : 120, 32, 32); QLabel *prompt = new QLabel(prompt_text, this); prompt->setWordWrap(true); @@ -236,8 +251,8 @@ ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString outer_layout->addWidget(container); } -bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) { - ConfirmationDialog d(prompt_text, tr("Ok"), "", false, parent); +bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent, bool is_long) { + ConfirmationDialog d(prompt_text, tr("Ok"), "", false, parent, is_long); return d.exec(); } diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h index 2cbbe21ac..700e114f1 100644 --- a/selfdrive/ui/qt/widgets/input.h +++ b/selfdrive/ui/qt/widgets/input.h @@ -9,6 +9,7 @@ #include "selfdrive/ui/qt/widgets/keyboard.h" +const int DEFAULT_MAX_LENGTH = 512; class DialogBase : public QDialog { Q_OBJECT @@ -27,13 +28,14 @@ class InputDialog : public DialogBase { public: explicit InputDialog(const QString &title, QWidget *parent, const QString &subtitle = "", bool secret = false); static QString getText(const QString &title, QWidget *parent, const QString &subtitle = "", - bool secret = false, int minLength = -1, const QString &defaultText = ""); + bool secret = false, int minLength = -1, const QString &defaultText = "", int maxLength = DEFAULT_MAX_LENGTH); QString text(); void setMessage(const QString &message, bool clearInputField = true); void setMinLength(int length); void show(); // FrogPilot variables + void setMaxLength(int length); private: int minLength; @@ -45,6 +47,9 @@ private: QPushButton *eye_btn; // FrogPilot variables + void updateMaxLengthSublabel(const QString &text); + + int maxLength; private slots: void handleEnter(); @@ -59,8 +64,8 @@ class ConfirmationDialog : public DialogBase { public: explicit ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, - const QString &cancel_text, const bool rich, QWidget* parent); - static bool alert(const QString &prompt_text, QWidget *parent); + const QString &cancel_text, const bool rich, QWidget* parent, const bool is_long=false); + static bool alert(const QString &prompt_text, QWidget *parent, bool isLong=false); static bool confirm(const QString &prompt_text, const QString &confirm_text, QWidget *parent); static bool rich(const QString &prompt_text, QWidget *parent); }; diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 4109a3b6e..d3c1353fb 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -150,6 +150,10 @@ void UIState::update() { FrogPilotUIState *fs = frogpilotUIState(); FrogPilotUIScene &frogpilot_scene = fs->frogpilot_scene; + if (frogpilot_scene.frogpilot_panel_active) { + device()->resetInteractiveTimeout(); + } + fs->update(); } diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index 65880bdad..d8a235ad3 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -6,6 +6,7 @@ import os from openpilot.common.basedir import BASEDIR UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") +FROGPILOT_UI_DIR = os.path.join(BASEDIR, "frogpilot", "ui") TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") TRANSLATIONS_INCLUDE_FILE = os.path.join(TRANSLATIONS_DIR, "alerts_generated.h") @@ -31,7 +32,7 @@ def update_translations(vanish: bool = False, translation_files: None | list[str for file in translation_files: tr_file = os.path.join(translations_dir, f"{file}.ts") - args = f"lupdate -locations none -recursive {UI_DIR} -ts {tr_file} -I {BASEDIR}" + args = f"lupdate -locations none -recursive {UI_DIR} {FROGPILOT_UI_DIR} -ts {tr_file} -I {BASEDIR}" if vanish: args += " -no-obsolete" if file in PLURAL_ONLY: