From 89e6aacd25d9f355b9dac9c22973e6a5161b3f88 Mon Sep 17 00:00:00 2001 From: Kent Sutherland Date: Sat, 25 Oct 2003 01:35:45 +0000 Subject: [PATCH] RAAAAAHHHHHHHHHHH NETWORK MENUTOOOONS! --- English.lproj/Preferences.nib/classes.nib | 12 + English.lproj/Preferences.nib/info.nib | 17 +- .../Preferences.nib/keyedobjects.nib | Bin 30409 -> 39541 bytes MainController.h | 10 +- MainController.m | 576 ++++++++++++------ MenuController.h | 2 - MenuController.m | 208 +++++-- NetworkController.h | 45 ++ NetworkController.m | 190 ++++++ PreferencesController.h | 12 + PreferencesController.m | 135 +++- libValidate.a | Bin 8032 -> 8032 bytes 12 files changed, 950 insertions(+), 257 deletions(-) create mode 100755 NetworkController.h create mode 100755 NetworkController.m diff --git a/English.lproj/Preferences.nib/classes.nib b/English.lproj/Preferences.nib/classes.nib index 8bb69df..ee754cf 100755 --- a/English.lproj/Preferences.nib/classes.nib +++ b/English.lproj/Preferences.nib/classes.nib @@ -6,6 +6,7 @@ { ACTIONS = { changeGeneralSetting = id; + changeSharingSetting = id; changeStatusWindowSetting = id; clearHotKey = id; editHotKey = id; @@ -19,20 +20,31 @@ appearanceEffectPopup = NSPopUpButton; appearanceSpeedSlider = NSSlider; artistCheckbox = NSButton; + hostTextField = NSTextField; hotKeysTableView = NSTableView; launchAtLoginCheckbox = NSButton; launchPlayerAtLaunchCheckbox = NSButton; + manualView = NSView; menuTableView = CustomMenuTableView; nameCheckbox = NSButton; ratingCheckbox = NSButton; + selectPlayerBox = NSBox; + selectPlayerSheet = NSPanel; + selectSharedPlayerButton = NSButton; + shareMenuTunesCheckbox = NSButton; + sharePasswordCheckbox = NSButton; + sharePasswordTextField = NSTextField; + sharingTableView = NSTableView; showOnChangeCheckbox = NSButton; songsInAdvance = NSTextField; trackNumberCheckbox = NSButton; trackTimeCheckbox = NSButton; + useSharedMenuTunesCheckbox = NSButton; vanishDelaySlider = NSSlider; vanishEffectPopup = NSPopUpButton; vanishSpeedSlider = NSSlider; window = NSWindow; + zeroConfView = NSView; }; SUPERCLASS = NSObject; } diff --git a/English.lproj/Preferences.nib/info.nib b/English.lproj/Preferences.nib/info.nib index b826611..e4eec3e 100755 --- a/English.lproj/Preferences.nib/info.nib +++ b/English.lproj/Preferences.nib/info.nib @@ -4,12 +4,15 @@ IBDocumentLocation 6 66 356 240 0 0 1152 746 + IBEditorPositions + + 634 + 386 421 380 180 0 0 1152 746 + 639 + 386 450 380 122 0 0 1152 746 + IBFramework Version - 291.0 - IBGroupedObjects - - IBLastGroupID - 1 + 349.0 IBLockedObjects 281 @@ -17,8 +20,10 @@ IBOpenObjects 6 + 634 + 639 IBSystem Version - 6L60 + 7B85 diff --git a/English.lproj/Preferences.nib/keyedobjects.nib b/English.lproj/Preferences.nib/keyedobjects.nib index 1f5ef1bf94246e4b7ff2ae28ec7988c47fc12c47..5e523bd632ca93a8cd0182667a6675467b101273 100755 GIT binary patch literal 39541 zcmbq+2Vhji^YFG;L*QO$QVjtCm6A$tA+*pTp-S;c4hW=RE}@FPv4XufKiW0P{If&f@nl1 z2C;`V@aJcY&zc;_AKCza1+(&U(;F1zO&Z!DZ_I>1MnQ12uTtN%UV&+W%;ZqTC_?X< zzlE5@8kyR;nO7~-U*LD?az|Vw5u^gSku)c|ORATg^UXYuP&XDci(0 zv(K^o3oQSNZDrfocD94 zfAYWi8DR)hSi%;yM6{?U>Wc=Vp=cx;iyK4}(M-gOMA1^T6CFje=uDT16!xy@Dtd}O z>@Cp`-}lG&1H~XQSPT`z#0W7`j21VG43R0uig9A1$QHMVd@)%}!#<~r83M%J_;ioB z4}ae;9>(8~h$ry(lj1osN4z3l70bmN;!UwiyeHlltHp<6o%l$6EItt%#U`;?d@jBc zUyChbtJp4fh+Sg0*eCXjgW@M~So|V>6TgdN;yC{OL!1<+#NXnKxFG%!MH)LW{ zrS_)ww)U>}p7w#ZT3f4qs%_9VYMZsM@$Wa<7VTSYr?yMmqwUpx&<<*cwO_Rp+CA9! zAK3R_+TYmodF>zVqE@86s&idv@9LWDLpNm)dW2p9H`SZzt@Ji}2fdTtP4BMv)cfdt^#OXCo~{qo9@j_detnFdp=at@`UE{& z&(U-BJUxhg7wA*)_f&nlK107%zg>6ryY+j-a($LQTYo@*P=7>!6i0hZe_Vf3e@cHw ze^!4^pQF#!U(o04i}j`Yi~7s@GW}KkHGD1CSLiGCxAeF5cl3Al_w^6-5A`+rI{hR4 zWBn6-qrOT19Djd_udng71z+3nwH;qO@wFRYd-Z+#etdn8uOIRC6TW`N*Dv~S`tSNt z{TM!bTzseJ&aU*^}<&leD%ZE z0Ar9b${20jZ1{~aM!?85ZZYzW0;ABFW=uD3H(UdZyNr89BV(2^+jzi0N;4iYo;02^ zo;98`<`^#+^NmHuVq=N%lJPPvFkUfUH@yA+KNvq6KWiI|!^ZFU`-pMW_`^7D{EcUzE z&1Pn-8E3}hrwL{vwrqi~R%RQsE&gqDf6`Xw|T}qYo5cp z=go8Gd8~N>%Z_2Kf3Tm6W|4W+A{Mn6KWA}ESem8Ni{?qouuRLcY<^C(pf#)r^AGtK zdp&AZz)vErirNxdL%YYSWL3nTs#`uB@2urmQC4M(ndh;mD%ujOs#VddW>vQ;(roJ* zZ2y;4!>YiWS~YRxvsk~DRokj#ndVv9mw5_Ds*8PGYt=?5PngH7XsaH5!-_G_;z;$e zzXsS-4fBY3+^UIKu7P!qA~e@ojjZdf8|~`$HFgcVrd`XfZP&5u+Sl6Ac0D`Bu5UN6 zud^H4jqJwu_4W<+jdl~esol(OZpYejcD$WnC)!DN3%jM=%5H79vD@11?DlpCyQAI7 zPPRMSDRvjTtKH4+ZuhWL?VffoySLrP?rZn6``ZKTfp(gmZV$2t+e7T3_Aq<6eUm-H z9%+xVN82~setV3aVQ1O_d#pXq9&cya6YPn0ww+_=+IjXQ`xZOj4%!8FAw6tQwx`%r z?P>OOdxm|hJ=4C;zTI|h;0NtH>^tqd?7QuI?0fB5_H6q;`+oZY`$795`(gVL`%(Kb z`*HgT`$_vL`)T_b`&s)rdyYNVo@YOAzhKX|7uXB!MfPHQiM`Z*(SFH(*I>cUMueI0NAKB~ekL^$FPwfr% zMthU}nZ4Qm-2TG;(*DZ++WyAgVsEv#+27jR?H%?`dzZc2-ed2zzq9w*`|Shv_x2C= zkM=?PC;O27vwhh9#s1a)&Hmj!Vjs1S*~je@_8<12_Fwi%`;>iJEV2K#&)8?}bM|@r zg8k2+2DydV*#~@;NF=F9DiI%XNEE3|s*tLr8mUgMAvH)%Qj63kbx2)uEr}-eNDQe@ z8j$NqL(+&eCfAc2Zt9!X*PjyzqRtt_=WhADbPaIy$^ujeaKA4XkXR-`GK*4{6KC-Aczwy$j{5p z4&-koaU`B3ki?JZ!DI*-N`{f)xM|Q^-^@jZ7yq$gN~1xsBXT zTms|{awoZq+)eHw_qrCa$-t%pn-1(&V7CEtf!zV@E@1Znn+5DXU=IL$7}%r09tZX$ zu&04N3v3RsdB9!(wgA{7U`v3#2<&BGuK;@u*m7VifV~OqZD8*Jdk@$Lz&-@F7T8C? zJ_hzFu#Lbz1GWd)cfj@o`ySYjz_mTT?tsWo`l84B{BnIiTOCVT~mFt(}KBRA2 zZ~vG;HZFErUSWPl09g?$09N=Nd6Yax9w$$bC&^RfY4QwtmOO|3%pr5hJn}qwfy^fh z$U?G+EGA3HQu5+xpVK$Z+YxC6)3O77{6TN$d1amb856T|$Kgimla-s6H9dfEc%P(a z6=avZQ?!h9<9#*|HzmGF%g7G|a^)M@-al2g57!x-6_|p>L(;ot`^U+RFAG#RKJH_{ zZgDHPk!}RANx<@5U(}?jga8Y=6-WE3&6pA2Bs!sG%b880XCx)W;&WolnKN+<^$Fw_ zrWfW0g3+Zm6C%k2Yb$vbQSlmi9mj1JEXc=+rOQ#>O5P55gREFYR*;n>Np0}rJ)kN| zOsj`)$y?-YvI;wT2f5k1j2Ah)4gr< z0hzUctahWmQ(dhgYsot7@FTLGd`v#Ujz1+EaCsJ!jbsz~jBF;KGN~xAXx3cA?xVXczqfA1GFA7+$JHwmKH*z{#+$!5c z(~-E@Mpom$Z^?UZRd)Hu8@a=GA}ZJ5+`q#n`^bK>1^*qu z*?v!cAU`6wU2`QD%0wQN?jMJGP5$e1hW0pM*E`rrWKc@<`y9C{n<*GBxmCr+>L?M9F#bteU&9bLq(E(2XYI7 zI0Ia-NqPANNF>FDQqp+rU9Jn_xpQGbL0+y}mw5cJ)LSVeaK@n}^ZYVhZz;?|7_fWX zO5soAeVcqW+&XTwThpx()uY{jBDY?YpchdjkM`9{8}H8#L>G+D3PzXgkV!bbK>oMn zA94|~Q$z`+lu?dYmirX{NYRA!Fjfs8liZ&Wx0YMmS7k)+!i=m;fA@SpQn=bVR43~e zA`vHTr6#FKEovj=3NK2$)pf6x+hG+~B5pm0FQmOK5A_hPI{cXnXfYw~gDu z?dA5{QHlxdr?L32BiTqh(PX!Y+uCh9+E-=9j5tJdqT1yxk@d$V%$&K4b_s3on&cSm zN>0#jv^(vA1Ei9(v?uK)r--aLB`-TKUop!PKB>ywB)7TS%x&eyMu|>@j8K~-Gg?w? zo0478SM7>^G0D-$^=fUW1L#1SM$-}TgXmy7gbt;{kjJYIaX|G~_8|AB95`)~KSN5% z6!e#7=Z#a_A;FFJ>292xI5=%u5ZUsG438bC2HWUJnufiPB6C&GL&j$n1bW3bYvKL0 zMmH)dg!E=QU?Ckql1c+KhGuvGxh>rm$fH8thLKR*^;p?;=h#qB<7w7nn&s^=k4_M2 z-LeANnI*evBF$cke%74g{S-p2sx<<)q8viKxqK<})KX4z+q&(-3mMl!vg@laWDw~j zw2*H5Vf|6*Mw=kB&2C96baE+?(L_f7T&%uUWxX&5^5d1xi6xN@jqRnW*^e! zlJoQZ({R74zldq2NXE+%5@TC5iEe=-c#U78H5PfY!&5g~%i&wAigAdDmdJnx&%lS+ znL9&#D9nY(Cif9zQrzL&t(_n>)@O@5ydy zE_egRC&^X4;*@qEy1W7JbhAeLYWB@57S5jL6dep{*v&2ONee;!$%3G`)1E%0>EK?bILWk8Yk*`4e}N!ee|AD#D6D;}R02O8hU>V0s9Z z-c9spx}F}UzmPK&)nNK7{mspHZ}CyZ%kH_OQ;{2tGI8S~`}S}i9g>tD;xA=!M}6qz zKs$?8BClV*!&=8FI}P_8;=YCAvx#y;wN9E@?oRqsAw%w@sn}UcVZKyMvZqBCuYMa3 zz1Ty1k|a~l8bkqu*5EZWW;B(3H^nuUA4y)8kd!F5IJ!aT-*Q)+p=aqidY)`{Zv{$# z9sriK1BLuQs5;+I(R+oOL=jod2&0UlP*2Uo@w3Ke1@ciJ3K>Z5O!uODn_D!ZPdb)# z599{&{n=qNk!hqJ({VfA?*5|&W~TI_RAm;kSp=)VB3VUNiTTiD6vZmDDy%B2#;UVx zSPfQ_)nc_-9oKd5a%Z^@xR1C`xX-wA-4*WJ?uYJ2?q}|o?sj*d`-A(dd(=JUo-IR& z2r*^XNBq2fK?pBK+$NV?6>6vKkMrM0b1UqdaO*HpjMVE_l zeFiBFwmdMJD==bNoIBeQ4cz-YP9wo+rM5_m|7Iw%L>3W(A~vM7-#;ihdrV-3J{}(|xF9aL;5&`fnBkYsV^t25-gMD-=AeP;eKbU~qDN0mk{u1EZ4yBbjw} zA9X}C_puT#;o+dQT$KN?5?xtj2#n+q4jvzl#zMx#==7|dz~z+JLs4F;`=tBSXkT^7 z=q2g3RH|QGJTj{*lU{FDF-&?-gw%nyR97 zZ@5-+s#hfRG*&4*+MJQS5uIo$2cmOPlPV9dgVi`g*id(#BXsw9kHjTAi)*R)Spu$a zeEiIrdubPTQ+bLb8-Yu6ij8DGn#zVqm4%H$jk;T2evZEYJqYds_k}1Ewa1<8X69#O zSO&`^HCcenV`EVVI82+fabzPp4w~iokr@Sp{$^wHu!Hbh`5c&v4k+YR$fla1vtV4o zcv+eo7>9y+azOfjGxBoKI4S7o^AW8<=SPTB@W=~|o>*J7W1Tx+=_@RYO<2e#NGrXj z^gd=}<@mGv7Uqlz2-z zK7(vzx3Zb+Hd2o^M|OBSbMZHNY1y4PnY+-}&@49#B_&#^&HU0U6&#zL=PwANrq*nF zAU{u4@aM`uIerub=*w?5Nm;U!1BkNxAadqr*{F16qvyi&^@pdbK36TXa+TH}{$#8| zxI~L`o}o9;`@Fbk?{kT&OmFV8YQgxt!XP?6JYdKBgT)0w_homnyUcyfebrs$E_7da zm%DEah#lB8b{m_84wjW{HoK4AkB}xK#93ns3j#rRsr!<f~0No)3yobp7B zN4PI~Yw!qrbRm0Gxj(vODJO?NA029F1G2~26ARfBa(U24749_Cb5qJd!%B7^Iazx2 zr`=c3tJC*#x_YjJt|UFs()6%>-R1SAyCFn}vVM)S^#?Q?(8L3AK3lMeEx>JnK%$@7 zBc3p!m$$tZDYmtkEpb;m{7?5yPimpfAK${$LtCSs{$KdkOUw!Jt;Hd}_10)#joyBA zdyX&VX7Lw9qkW#0yVrAKmUgtD5^w{1MKT$5qMu{0ve($_sE@~ydG0Fr1NS}meNV2x zfx0+b;lA^2$e&RY1$q>F6Gi{KNU|lShMa)ZPtdIqFBO+o9#6!_a+RP{?47U@v|1Vs zm-E{96;JxWUE{9x*gSHh_*R}mgxy}5_pM=-!@O_ZkhB7SJ{l`mLcvF26s%_-vrj@O zSnqChKXo^FDA*uTu+jav1O-)4qxlR`@Cj0B2?`RG00^mqEtLybAt5mVSNaNA_%e)z zP2~~J*NSkyVO!kI4!_`jUP3qtO6DiTCjB?U`Ic1;5zaRu!uevfuTh9_+9^4d-ywR+ z_&{zcGRjtT6$oAlhdaYK+{JdYJs})^zlY}>m!SO2agtM9aUXM=)eO)12(C=XP>S{x zwA9?Oc~_V@k1U=!ugEKLUufpX+`rsE+&|UKc@)l^S9Xt=%)Az>#jE1XPn0DBX@Z7{ zAW^>Y@R88sibTL`gb{MGoL$Xp$z9EB^E&QnN3?SPR;r}3bwdUP8Z$V~zt>}VG^_1x zYhF9Ft%I8shbTN4Xjkpq|{xGCtcm~fbhBOjr6i^>f$AfgN zgmfIxiY1V?M&xHnNGo0LMLfkPmW*Aw46~5q@f^i0a)DL_S}n{jLQ+4jrLsq^VB7Os zc(HT+PN3B>Kv*0WLIP~otXXtQG0V)$t#?HiB`;9(o`)+`$S3nDv^k$j57RiHHGp;o z+6ibf(3;-lr_0GtE#;@=w<6KCZ-MJ^+?vJP4jUIlcgOG88!%8LE- z^9u6h8#KU*y-@NkZqeem*d@N}<3}}rYAAM#0eM;J60bhcwm|CuZRZ$!fVKfz7ifE+ zErE83>e5)b+|Y2A9xPApA)O+_@%&f(Su*Qo{v4mf=kj^{dHw>rr6Oye`Z#m z7m4-w+Z%kvBEEvFj0-gCyga7jUeIK~*izXt6=3FXNgi-r*v)tV6VO5t|4ZhF!5SI! z=kM@$)ePUmj0!o!b292r5A&rmFyYWge7%4+-cJ6(kXlXt0bk8O{k8-4)Z1d3Ka)r?5W+l;Sl+OAe!sSZ4R^<(B}OdVfX1nZXkqX=)KHOy$Tg7Om5Vu z(PuZ_c;ktrq@)w|>(~FRX3d(D8DsUz)C|25LbU48p~LzG3lb-k|ZtxC7$oZryBd-qK%SFS8Nd-kl1Y+d%VcJ11tAw!02!nO#f`mr!j z`N+bB3yb#d-CK12{CV{{ckZ0(L;jUzhYuevdiB*;i#m5k#{(f2rL^9(Y18(8{PD*{ zJ9q9Z`t7&hivIZHk20@g$BwB!H*DBYblYvW?Uij1#ul%<^2&uRTehgre*E#rqMv{M zx#*W)ekuC(*I(81!Gi~j_UzeHv~AnAq7^GvT##)LADz~$S)Z_uL4I4fx+u#d5_M~;EPMy~5-o3kY^)kEL>XIT~-8~d)T6RL}U!ao*1wK5FD#lJ7$>&?*Xg;1HKFh(l1UzJd*dZn1TN~k;~Mev*y;eS#dD#yy0+9J!T#3yy+ z=lF~mqE$oXSVF58m-8N&ZlZa0_&Hju_*pQ_B=P!@eKs!D=fIM3Y$tK={j)E)hzK7E z{q^|e7QS^6-^#a1e+u%;&RGRH{z-~4AqPRpRs87H-cFY+l=06{@R;wE!Q(Em(V@^k z-_2iJgokhTV1f)XTGjS{WThx)6)XK;o6r6KYR>n2&3)p39G`z5>OlJ+JD@|PYo5;e zADi=oq4ACXwSz;U4$Pr5641{PABW*-6iM+jzay9j7m|u4GXH0kiBjS#d58+!ujCAW zhn(f_(m7E*S`8@j=;wESWGO$wj~1tWU>cRD)C@y%@t%~1jcSRCu(g&35h~J38%>nM zhYWU^nAZ|j@A9dRS1w1Q&w8i;Ckr zLX(-6yq(Zd586l%qv)5}a=5MJ9~u7|Ej`p;W+^5xbj=(mQ+rg_Ul4s~1sIk_HFFT= zkju*5^7C?JL)8cq4)UfHYuP}%dqJ$^izG_N2rE$3KY9Y~0dz3X)RK@YvM6*r9t~?c z;fKBatcVa5M5L%FDhZ!(NKFwXD&usj;IyiWYPh(kMRjqFs3B@fWi&UClb4&7fsuLC z#}u&mR|U{e%k7t|Hb2f3Q{h#DAue^gKYv_6CK>k0ni|Md6_gXVxJsYAOqnc;OwuwlDuC}Nn>WXVogD%Eq5NI0EK|s?HMSc8PxzS$2 zaCGC?>qDBQh>@Bl(7p`YVPgYbbC-%i)Zkls9S6B)J?O zkZlxYi|hHhg-9Gp3ZLRe^7`<881u=Od2T_aw27vu!wK%yZqCmxS?UehGeVkw^03Fjr^!jx)u1^ZI8Qg#+v!lJckBia_* zm&1UL0y+ZdNDt!n65qG)2DH2AAySJ0^#dIXG!rN$i(=qi^pb!gnT#m`v;*%T`bt1E zN_=S^Ojq`$i2)@bjl0Y~7HQHx7U@8wuG)v6?9DW!t3!8$yXd>6z>!o1Gj!2E>AKhX8AGsG9wV} z4OljLMxs{u)qrDw-U1YJ8Zm&-5_zwe{oML$4nh&&$tA-DM-KMqW(CJbs}$!;1NmyC z@sjI`EHMFSp<^5aI=O`9#wB)e zgQMZi?~&!Lm=7ZI#7uFUxLu$R;x?cF^e&+Hdd#*It1TH=+)--SJ8@X7f4gI%-R%zT zf}z>G?8$+vHNUr1^I6z@Hb#RT6K!@(x$vcBXJrPW2L~|5kD<-%X;gqA<>T#g6921=gvj_Fhnw)oOR9w-^Q)Huf zT0A431$sZwM;)^o&mQ&|0Sv%QS{ z;^Jb7>xt>q+zeF@QwoY>k^RONSCx(*Jfos2plDQoWl>gc@ni8q)K_{s{_`?(uoW z`JNmYUk=k-qk3Np^l6~aczjj)Pya>l>v`7_zWVII#BzH7RQ0|A=p3MPz1|aLkn3Oc z{u%F9()+xDEz0Tr3)TCVKwkhl-|M|4`hovV@89t5CA}{g*s7e~x2fLIr@aX1V%2+s z3{H5xD`{{=yzfNAF_iTObV*uVY_J?vSnN@oY_IqZ=!=fYfWD-5Sc25MLvfN7g zUL0E{T197!_vela1k0vaNhYsRiFpm_?gOB!OZ0*Gq)`5@)FiK@@Ms$ETY~xzQJ+tf zaD+z3RN-w$h0=ImEgNwkVmCDAHNL8{qGB|+Uls{-^Bpr3~H z5v`i2t5pYz%x+^@RYa?as)$xgtEAP|>S!1f*aY-*pkD#qas?GY4yZ+!8V)retsYSH zId8sNllrBap!%UT0Qv>cFR#|5VW}plXlNK~_!{UpS8H;8sV1mUXg2`e3Upg}E8ax1 zIIXGH4Cr>ptN?UJDHa#2^v?f*#c6T8e=&>GnijLTo#BPNj1g#wa&Ky=#_a~WM-g3u za%jY>Ge~jq7?erC9KU_#oHGo^RAe-ToYGqH0aT?|%ZQ5Bn&3+A1^V4h>3Ezsy41l` znJ_(4+O37bQ1K;kUabRJt##Bo$v7__HAdC;hXHZn7_XK>>SA;b(3_| zUF)HxYCSdNN(X`Af!a#IZUojm6xh@HkX2e=@`=_@>rbyIHMIfSK&=!0OViS|LE2#Q zg*F7Ahib!6iIQhq(f*fqG8*zd&|X)E)X@hJRFM%Z99&$8o&)bOIj0~9^e3Q)fLXwF z2NSJv4$Q{XvE*n%)ax1ii!TrEo5-w1IOY&-&>k-xOY5VJ)JAEe@!Ss{kkxL+Np;u! z+8E8x_iGtirY2)`ox7`(FZk3YFC%nhEyFv_3KT`sVW8-rxae3oJoN?|ho-3J->+q9 z6J*ksHc`vgaBY57`ED-g@HLb6ettWD9T(%Cp&jP>;h zWQ`kNfY0iruM8Zb@sHv9>;S@3n3Idgf~%u@^^)l0kgUvt@ra|8ynIX;m*8T?`sLNa zop;aA%2e}DbV9FosmjnQqa>5^cc{SFH$CLwwp(+SA%IO*bKVr`8_qDV6-*;$k1AnItK@JWi-`XME*6O|R4=&pT)v_Du| zuYIh2g3JL=b7x?r6&MGm#dOR|8x2e-%}ecxP^diw;)v0idBqIP^BroNw9l4kpB2v& z0Vw|b$^d+h!+(JQe5ri}%y1};={WP@-M~!DL{*1W(8R%`D&BTfiBrmpjy~>$MCs%9 zK2YxN7IP9vlmRA^^;m?iEM<*G2$;JhuCwsTKC;)=dW2Pw> zFoSoT95SbL8wG7J8nfl6;h`-&-0UyN%MV8TgVEEl*X*o`fo4}0hTka+?$h>b2b3`U zUfU;yAut~>ywu?aV2yw^@R;L|Qf}?jzSj$v7{Z9f{9Mkj?vlLabVSe)c{r< zSXC!tJ+Lb3{FJ;WM*5}0Ou4j-_*2OUc8v$hNy+I?X{UkJ1a|G^Ea{MTMsfPH+BwCN zSZ!dnfYrS+1G<0=nbj%gAc_l@$B+&c^T~3?)QMz+I@OssW<6lhz+$dC=2gciZ_~;} zO4apZuAv*it^?Na%2lo6{jK9QERBI(uas7m+CECs_Q=Y_qi~_2(j`erZXP}~M^@{#^*U0^n+U8$SV`5dCH3@ZR8m<>U=tLb=`naO zh+bcBpkJpq1l9^zJ7A-Ll|53V*TH{{75vu&YYhw!{z>*I(~A>5=c4qz$0wm!DaW-% zHt9|D`eFN%wH@s{ip&V_JV0JFKCd7ek6bhf9q7c8v3NK@o?k6tXYw}HX3^2N z8=a1duLUxZ<>5&VyuPDJG#(0xmRUS_QVEX_VTaL}c|0a`VGr8ZS$N)HN>+AuG^T}( z!#k=}wP5HBX?RHFTL@S_M(ZX^KKZ}HHqh(BgRa^BupXVGH@{Tj*JBlWbs{bju~4iu-S z=v}m+9`C8@dRJ`~F z8?2`S>jJE6`SABr@TVaBDF}aW5B{5gjTq*2j9w8Xv-N&@|HXQLPiCvAVJWpyg0@{+ zA)d~zx*X_nst;p)kjN>TEAH`@ZP}s4DA-$A^!(6 z-=w!IX6EI?6+pPg>Uf+@AMY{r0Wm45GgkuZ4=inv+(3EdfitlLoPk4n;QmFU^l~;| zYIIM(Q&>Z`>S)c9w)5 zx^iSgsHZ}0FuHy?o#YCq4EJ`DXfV5s2U95VWF7?rA_yk-dZRUH^Q$n)X)9r|@N2bLLSwjpFtN;c|b;V1{aTfbLEIe=x9?_W`{-KSu?AJ|x6cN*h&<7bnxnT#pQab_cyaM2 z^(+?^e<*X+*bMLb2PLGXMbk17vrmT4h>t_NIxfC7E>z7K3C$8?<6FfCZj|d=>P?6n z%+sG=s6QXd46+PSyT`h>^Vj~fEMTr7wHT2MY1L)jP}hM)2|ROU&D(&)b)pX z`M902F#I-rd7O4gUm`D$12$!(RMtyreWB~+G{63mS7X`;j0;{~fyC&qkcYKHz-A1W zmwtOUG=)dNk6*`+kr>e15bBfp^f$adZy%+O?xyA@XHJ%p?UE|C{-#$2Mo1%GWp#|n zn_5yKLSN-oxO1dmp0vKaMg{#nug2Y@@Yov4=gX@^>Z`pf_ad_`kcXA!S>?;?Ir>_! z9wv%c^^R~Q7xb3L0WYhI6JC#X9|U&)&`cEGc%HUoTKM6o`UYth;O7rvU!E^Lbfg1s z0+A;?JTp@NOy9gn-|Q_aW`}$0Cog@xd>OIZFR)uw+#U%d2+O|0GBiRSD_!;tmZ1*% zMCr1vScZn*Q>Dwk#j*%s&y+6Pfn|}vo-19p3(HWipIf?Y50=#d_I&BG@AM6rx6d%B zR@0la=g9Pkx_IKH8yvs}sOT*$UG@W(bq2P$blE{HL$S8BblD*+L(Su*(q)IStPikd z;WAcR|J7Smj6+pci|-xqxok;URsD!p^Yzib+TQZx0cX4%4T(UV=ew*4V*fY}*bmqn z;Q_Ji4=h8$wK81B>ga!YUB5L7txjD1%evy7_0wLJRY)-MdLAze#0wl@dQjn{aCbaa zKkL<%p`wzyDpcl$gG=i6(Jy#)-$$+?gFgL`nx*A@IS46wkyjO?Iu7ns%nebgA(wX9 z&tP7?HQ{>Yg4y5Dym}a~3H4VVh$O}^$-_F5!TK=RdOO4R+Tw{(AF977*@NY=B_mSb zprcu~VMKH5qEW}Fi~lNXV~omt zzj3Y76L~D~roiJ+2bE`%;#!pP+Zr*R2Kbedk%=wPYje2@hl^04b86RB2QNpxD;W$)MP8hT`>PyM*=4l#R(daTr@~;s zyS_$wLF84Tmu&~y&gkGZ*oV7J_8Ta^f9&$QZH;8Ft~{n%rmplEt4q;JXmY2~#jE^7 zapfyRft#bdzJcrnb}%eRB;+{9a`>syQ{O;wBONLkEXL@KWo>~SE?w3a``iWW*V1ME z^$iBPpneaR83T$C>-_vZ$(F&3cyD&0+9)f#pk7#XCV zkttmVe*wp{KWOYox$K@xDDF)tIeAcOa1lT#xC!2_>V|;?QzvXpn{~@^!^O-TZPEccpajP-2 zbVwVqbDI~^26hqHKfuldyAalm!w+5n_p*#)V}=Z68!FAsnCfK|8+QUL0!~7KY~yZG z*SH5b1Rsm7dh2zcn9w%pwXE1WhE#UUx6sgN%N^dB^t9+2=hCrHUJP*8rQ-uIW*E<3f zuWlbw?!EsC&;lbX1kgMLXrTnCBJfHB`}GF##M!II53Xxz{V4hVOaYPnxJ1y=lCK15Y#FHdXxY@@!hJ%`*Kt~EO^fNz*ud3NRQIy#u^NK#R0Di zygu-Hz+-@4>rwYQG~10e--g^rHI4N~j*){5i$|kumwZkhhgY!Tb8D5$5H2p~Xv8LC zgIWW;{0w*lCI5>$|90HyY(~+Q>!xI$-6Jn*O z#dEEz%<1wxH=&Mx!EP{WXZ&jX2E0W~$M#RI296HV%R+XZ;#QncU*h&xgxCrRu|)iM zV&Ir@%s8&LP%YyG@HW8P0&iCqb$=RvA?i*d>P`V38|Nc{2Y`FHPO6-fQIeykJ2&DpZVVp|*lR z_bF+WZbo>m`k}eu4dQj5Ha-w2D5=@H!I8aF;Ra= z1D>w1F5@zxtTQj;vfNMu?QT{#of7Uj2q_pd!86dGFPWSNSkQ9i_Kc$7_N8L zS?%`^FR+`jmC0OqNT$oIxjyuwZDu5%ZSJImye5v2BHtY z`CtriJk%itF`_Rt>{X*$W^*&9WY|np?LC}Dhi?@{uexNX$Ot?cTm#SVU~m`pgEH7c zf=ohMK_Z${orLw;ab?T9kxUsS7MK28CnXc zJ9GS=4!Itk_~zxlg{U^PHPU%M@*sagufqpu-N`QFR%YRy1bwtxtdf|iHzd0;8M?KZ zY}92aWAK`dHoPafi%I8PM z7ChaPXEYMG^XKvQ)iY$ZnW8tK?M1F}jIJ{V@&$|v&f6JTysxoFOU0W4II!0x)D^Jnx+SF&%imZX-VyIw*}|fX3H&+Z zezMbO$h+X358W^s?GU;>29rH(1zp7|Xq|-1d$WyNCH{ie!|XxNYpB)~0G|weD)4E* zX8@lG{C425&pYW~=0szeQ~vT61G8u7c9s3|b`{Je=Y@mR-2?xwbts9JA1rP?C9E-) zH?I6f0lt{OC`U$zxp%w!VLVNVwYlm?`d2s?iR6L&h@7fK$bbfbu+hw@7T}I%Q^)YzaOAL4Ec_h3}WQ#F_Q6!Z( zts)X}u3m?1;c+yUyT%sW0O>Lt8SAgci>p_0SF1=@VNju+$i?SFxUpQ!k)*~5>>pP!DfUv#T=34bipX3P zYw;LMX|^Jn%GFp3?=28FlBHnwYef$`qZD?`(dNyj-yCCRn3-n49BYm<$D3K^1aqR9 zZRVJ{W}Z38yv58ngJywQXihe#m{ZMZ=5%w0d8;|oyv@AbbWJetFz+<)GVeC;G4C~J znX}FN%=^s;%m>Yf%!kcK%ty_~%*V|q%qPvK%%{y~%xBH#%sJ*d>(MLbYB2IAMhF)z7Y5#;ERDT z0lpOYi@;w3{xa}oz+VA8)63C%d>#05;BNq50emI!H-Wze{B7W9-Ms_+UEuEle;@b< zz)|!55cnG4Yk{u={t@u?z&{553Gh#WZveg#_$I(RR`_P%Xcm6~9CgI6fPW4A8{k`j zZw0;$__x5f1K$CBC-7ascLU!8d@u0tfbRpoANT>_-vj>v_>aI30{;p4A>cm)M=bpU z{8!*;3;YiJ2=Jr8j{!dp95whqfd2{nFW@JEqq=??_}{?K06z=-9PsnNF981s_(kAF zAP5Kwf`Q;5@Eb%m5IP70gbBg|VS|VOQ2|6Gh>9cARDMWEYgA_vN*x%EiyI^lqX#kk z=j9+u^(QV4QxZMBIx!ye9>O|wyiAc+xhjb=+qBp_86S_9nRG+Stf9DYoj94pTwDk5 zT0_mcYyp;GH>Jic(`iEK=JD~^98boV?hl)nTPVK)CtNQ+t`uzXvdfaJ>M{Vv$Hk%N z5Y@FfH3R&MB@z$b6OJS=+9@?1935*69UjuMF@aq*4uMo^6bUC{u^2&eCU|)8lCI(u zFeMkX$rdG5hN&f!7Yh&eyjbOR5ihYBZWEtavWRjXULYo3PExJ0qJ)y#p~-rSn^*?* z38ly(L1I(gN~Mr5zZ0f7Lsw1{*FH4Ph;CWgfkwgTep4_tU1b1;$C4F`ag0xw0FqD^ zS(3CuB$pT$!m(P@;`ykrsqw(ZrX{r#u;b#SYA=Z%r&tC3`t7XO*Yv?(%D?|0h z$RhW!+S+N7CbfRj1@G%>g*Th~=~|LS29s{Qo9K;VG>XqBL-dBI0;J=`@cZz~@OIL5 zDENo6jieV^4{h|p#{IlA@1WO5;a!>Bi(i@7j|?)7@no$wel=bUi$E>EKkq}&lDo-7 zF%z|GpP7tbx3}1+Oje^Z(n+h!uEDR_>tlS$JL1VLbB0rsmf}Q&E!_IJGn(Wg&KS>{NlbXWGdb})|&Ta)k$Y`yEozsNMF?6 zI~n6i1}YFa`1L<`;MIY>$%A;+;Us1mBgAbe`s3INvmSmOV31Yhw0sa8*`m(|lN!&>ox0jYq|A?wZd9yy=lE=y=|?s-m%`b-m~7fKCo6>A6jdy zwbnZ8BWu0&vGs}dskOn{Xl=4Svo>3wTVGgTT3=aTTi;k)tgY5I>sxEPwZqzJ?Xq@T zd#t_Ich){@zjeU+-ul7%(K=}TWF4}8whmjrSif4oS-)FHtfSU3>$r8o`osFu`pY_L zow80_e_Lm)v(`E5ymi6)$GT`0*~F$cv$-v7&DL$hHf;-2z$5GmcBEa=u4Mac$Bwcq z+g0qUb~O-{K~w=z6$Cz42XPIE8X#(ds0E@nh&mwZg18n$G>Cd2VnEaf(E!ACAR2;b z1fnsB>p|QA;zkfnKr{u>3`BDfu^{3=#Dhoxkq9CQL<p zL=K2t5P2Xbfw%=kK8PTQ0uY5DCWDv)cn5-*24Xsh86a*2F%!gXAZ`cYf&dVAfVdOz z8Ut}Rh#5*9~1@Rt;_d$FBVl{{lL979>7Q{LbAAwj8;$sk>fcO-| z1`r!TYy$Bah|M5A2k`}nFF||-;%gA!fY<_JD~N3%z6G%z#10TULF@vt8^j(EdjXGH zi+v#WgE#=_z}cG5I=!91mb59he7-T;#UyAf%qN75fDc~90PG2#0e08fcO){ zUm#9`I0fP~h`&Lc0dW?@IlzOz;)27v_!1m;okJ@+^jTk=ub;!7bSVC=<!-Lgv*gXz=&DY$a4IGx}utyv=+hNNcn&oTl$XgHxIP8Aka}I6c&?w&^hu!F~ zhaJ}4q0jr~I&`2zlN~n2q0FIJ{{@G2^fhtVB;OE+MmTJY!*K8?9M;aE^Bt=BPC4`% zhgNl1io+&5w1-2xI1C%6JJfO53Wwe7(5D?b&tbPaw7bKi9k#?b*rAAw`VO7oL(IJH zu#pb!?Xbsv&p5Qc!*IL-zNQYF?$B-ytLM-P4)Z(oNr&C-(5?d;CKt>&=K4tv0% z!iQ+;?NDso*}+RT89u~2tg*uoKiG3UhmCevp+gOaMmj9jVGAAF$YB$GNe=Dr(3>6B z++jT(_Pj$A9a`C;6CG;#a1ow$7|x@OL!WYJOJA(RW;qPuDsb4V4tvyLFFANq6&>Ty zT!&5Yr912vhxYUh_5I^e?y$QYy4YbkK5SgYVL^vBbQnUv$YC)Kjdth~U%c;mhdt)7 znhs5KXmy7!bXY5gz39;A9EPikg-H&Z@30m=oYpdj^>G+NfxXssShf$V&U7gLMl8*9 zD7vF-I1E=7XSl$Jt#B=GawskgQo>w^-RDp|l-b@l$Jat79NHJ{qJ?&m`r@qA&feDw z%aeI{GhGc*AHTmijkHxRdeg(gr`eXcx>4W%n(M1t@gfF6gL|jD2 zh@KHcB1T0_iU>v&Mof*E5iv93_6Uf$E8?Dr*%1#!JRI>@#FG)vL@bDSEn<1Ziipo5 zwnprY_&MVDh;tQqg=!V9t#Cty8!I%e(4s>73Oy_IuQ05_ZxxPII9}n;3a2WZsc^o+ z#Yh?{B8^BpGBVN^Svj&=WR1w$k=I7XL|zx!IP%8GW|47`iIFWM+eH4qp6>gt$+PVP zxU&wtvq%UDgcSk_BqUhHkN_d%>_t?rk05*1GGi zTkEQ$T1T<+`0^ajalF?L*ZB`zzg*|{JkC!#kO^c1d4K{?16n`_lmg{IC13$;fE{oF z9>5PY0Am3VXaObwFn|G*fT_TAU>5K_Fdz5<_z_qJtOQmA>wpbFC$I(B4(tN<0>1!< zfG*$?a1VG2`~|!NJ^=p^c!YF5rJQ1!DF-Qs zC?_eGDfcL^D4&A|1`~qm!O_9o;8DTF!6m_l;PT+c;ElnZ!569hsRU{il}}Bf=1_H1 z6V*bkqE=JwR0q{Xt)(_m$5X$f&Y;es{y<$tT|-?*{fWAf+DSc5JxM)H{gwKVHjqZ7 zv1uGyJdH<7qzPyvX);<7t&~HFyy>38V&=^yByLi>jf4<&|1gi1rx zLo-9OLXDyGLKlSo7`i(2KJAWW){=PY-3JjPGPQO zu41lX9$?;K-eZ0WBZX1Is9}Pz!my$+f7s-(sbSxRtqwaDb|UOl*dJj}!(N2F3?CGp z9xe-)hi8YY!wbVp!ad<_;S<9rhc6A^8Gb4JxA5nz0W20Pj+M;%mX*RvWu>u7S=B5b ztB%#cYGRFJp{%*A`K*Pk6|A$Y^Q?=k%d9J`YpffrTddoxyR7@Hhpfk}C#+|z7p#}8 z*Q~!-?^y3yA0vnn-vi z)cvTQsDGn+qiNA8(c&Y zC&t3DXzX{f-^Z?vJra8?_GIko*oT}j4vUk@adO-o568!;<3OBN&IHa(&TP&q&OXjH z&JE6O&IisXPEQ;;Ze-l3xU{(1IB%Rku0F0Yt~G9A92_?%Zf@NAxWjR`g%%MvRRO^KF7C~<4z_QZXO-HB%sFC^Yc{FvC2_$Bcxe=t9U&)|piIeZh}%CF`- z_-?+JU&n9ckK;r9R{lgj!pHfO`P29___O(Q`3v}q_)GZ9`5pW<{Pp~e{OkN%{5$;n z{73wk{5Sk}f`Ni>1Yv?vf?|PQfC>VFDT40=GX)C;iv>#sn*>`0=L8o8&jl|9Z<2;4 z@sjvS%A~fW_9QeZkTfOfyQFzZ3zL3M+MKj4X@Aneq%I**7%NN?N`$#Wt*}g3BWw~P z!db#~!VSVs;TGX`;V$7`;V;5N!fxRS;T_=Txu{ZP z5!HyCqOl@OG)Xj7G+i`L)FIj^+9oWXdMtXC%ug2fH8n;hE0T+n&B;~CHObE8 z+T^-qC>cq{lP4$7PM)9qL-LQw%aYe6A588_K05l+=v$*7jDA1*Ys%0RAcdI{nZivG zrifFZl-(&GQa+7I86zG8joCeB@0d?xdc*_7!^9G?Ok5x?60;?R=}zeb>0{|r=?m#A=|^dg^h+9$MoQzPrKcIw z%xT}H%}kqN*BhrcK@#zKWs`Qd{UwVCdQ~LDu z&h#zm$I|bl-%o#({y8HoBO-&Jp~z5YXft#fzKlH?T^UC+PGp?UxGWne`$jfQHbTab zh07vku`+=yMJAW!$ns@MnMT$snl^vDM&X#7IvaQ+G*=^bF*=Y9S?ETpXv%9nJW1 z&oSpXbKo2-XHw4coWnUsa!%(w%=shdS#JN_m|RY7My@2z9WBO{^I(^S9*h&F{`Xn|~qya{kr)8wLLjcY_MV1=50y0&hWGL1V%4f{ubU1>FV53QiWB zDR`uaQE(JVihPAqp;5RLOBE{=s}!3RdlbhNcNGs5j}=c9&lR5)y~=*dVM?l!u4F1% z$|xmUsaIAj>y(fZSI$w+Q!Z34RxVYpP_9z0Rqj=GD^DrkD*sgtRuNSc6-^bY3R6X> zqE!hhfy$|BQnjmQs@AJ^sxGL0SKU`VQaw@qsd}w?tNN(wQGHPlR)?q=>Tvb9>NIt( zxr{tw>v-E!A4Iwc2so#oDFX71}l0_1dG_ zJK87OH`*^nw4%_Wu%gjLhN6mMQL(MqUhFFN6#I)Cid%}2;tj=}#aoKE7ynXxNY`IC zNH;_`TnFe#x?mkoC()(r@^mU)p{`ihq#Lhm*0t#t>6Ykz(jC`5(LL9_)V(PgQNk-J zEGaJ0m$Z~jD1l2Bm24~7S+b|(V#(W*_xgeQaDAjcMxUlP>#Ov2`my?{`sw;v`W59^>El(@2C^wZ`%iZPU%jcHwDc@gyxco@@@$z5GpOybv{&xkb zf?J`eP*)UJ=qnm37E~;%SW>a4qO0O)#m$O$#vmiXNH%hfW@DAH#^^M9jg3akILSE6 zxZb$YxXF0T*kk-+{8~Av5~vKROsq_*OsSMr7F1d*p-QZBMdeSGn<~31PgY*6yk7ab zve!f~kxh{%k?C7grm5JZH(5;$rm?1gX^Lr~X}xKqX^-iY=~vTD(+krp(-$*fCYfW+ zqs?M-k=bUpoBif?GinZ)mzvj_JI%+;SIsxf&&@B*y_S9!vW05lSh$u{OPWPvDYDos zcFTB6vt@y0on?b%zvZCiyycSRq2&+DM@x@&xD~Lnt)r}CtOl#g>an(2+pV*$bFCfL z?bcn^bJn}o2iBgd;Z;CYY}M#0ah1BtTvb&yu4+=%)T*Ubo2s@}bypp$x?c6H>d&fw zY{P6LY@s%ujc*g$+%~VR&equXZJcOBY#VHywk@{pwu83wwoA5WwzsyAwjSG;>gej& zYF;(JT39WvF0C%Fc2r~4>#H|bZ>ip1y{r0i^_!XjHNiC@HQ(0c)a2J#YZ_|C*376` zR+Ui|{vG}< z|2h9%{{#OE|6Bii|ED@qU2t7Uov^N`uB5KMZff21x>Q2_3sk>A6wC=CEf9pro z6YHbu+4ZC9$J8t8jrHdGy86cY)`sqeV+|)8&NQ5BxY+Pp!?lK+jf6&WBfU}GsBN6u zc(Adn@k8T3O@by-lcUMqw5(}m(}uBsjqMpv7*7ZJpb#7hjt0e`6wCnSU=ElMDnSic z1eSmXumUuJR6xDH@FWx03HU9fXBg8;IH6$@Dg|hybj(1?|}EgN8l6iIrtKM1HJ=4fd7F1 zg8zZ~LxZ3p&~ONVNKh~o0x_U)C=!Z+I1m>~gp#0SXcROCl0fNDCX@~3K?+C>X(1g{ z3Y9~ZkOi_qcE|;JAV1UqjfFs{1)2cC5C%H;bFInv0tq&10L<<~hx4oA)=L zYQEC^sQF#<=axY&#Fn@gVT-s$(_(CCXn|X1v@C2{*0Q6eyX9=l^_F`rZ(F{!2DOH? z#LHxAp8wH3?2a!VG2xxL*Xzu0*;1b;dnR!7QiC-TQ~(yh0|ae zoCW8?1+WS(go|N4Tm~CqGh7ANz)rXp_QCaV6FeSnhTGtF7=;7y6!<%MCOik82QP#d z!%N{6@G5vM{1f~$ycymG?}Ycj`{9Fd7km^x0iTA?!WZDn@KyK*{5yOXegHp)pTaNT zSMXo(-|$Dc2mS(oMFt>)k)cQsLO{p}6`>H5Cc+ynEIggYQ%xK`=IbTq!Ag1KuBvJCygLDG8viHM?TL+=JpA?i;yMA z@;(-J4YD5juQ0k5+0jQ&?n4eBhmj+F`r;|%SLA%3Ab172j@;@4q3pBwV*cCj=E3}>PH*Uu_%bPpc7CS#n4IURCGEz3;iCQkN$xEh%Q4{ zqN~w$=mxYC-HdKSccOdHedsUfA+#GkhMq*vpl8tw=w@;>3yMSH7u3*=(Ti9*v9`+FX z1AB)3iM__&V(+m}*k`O4?}rb>zrly%K{x>?<5ZlEGjSFkg|qQEoQLyqAwCixjf-(9 zo`GlL*?1nVz}2`G*WsmjIbMmI@hZFqcjC3U53j=;@o_kWx8f6V1jq5o_%wV5J`4XI zpN}uZ7vW3r<#-3a249D7z&r6R_;!33z8C)mKZJMTNATnLDg0ObJbnqkf?vmP;dk)+ z_#^xY{v7`ke~rJz-{YU~&v-BXH83DBI50F26d(l10cwCAU1C%_FP29g5F efl+~!fH)uzQ$poc1mN literal 30409 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 WPkP2LJst8;|(Ey>JL>cAvKZ diff --git a/MainController.h b/MainController.h index c2c73ba..f4f9f7b 100755 --- a/MainController.h +++ b/MainController.h @@ -19,7 +19,7 @@ #import #import "MTBlingController.h" -@class StatusWindowController, MenuController; +@class StatusWindowController, MenuController, NetworkController; @interface MainController : NSObject { @@ -36,6 +36,7 @@ StatusWindowController *statusWindowController; //Shows status windows MenuController *menuController; + NetworkController *networkController; NSUserDefaults *df; MTBlingController *bling; @@ -68,6 +69,13 @@ // +- (void)setServerStatus:(BOOL)newStatus; +- (BOOL)connectToServer; +- (BOOL)disconnectFromServer; +- (void)networkError:(NSException *)exception; + +// + - (ITMTRemote *)currentRemote; - (void)clearHotKeys; - (void)setupHotKeys; diff --git a/MainController.m b/MainController.m index dd8c51d..bbb0cec 100755 --- a/MainController.m +++ b/MainController.m @@ -1,6 +1,7 @@ #import "MainController.h" #import "MenuController.h" #import "PreferencesController.h" +#import "NetworkController.h" #import #import #import @@ -54,7 +55,15 @@ static MainController *sharedController; } currentRemote = [self loadRemote]; - [currentRemote begin]; + [[self currentRemote] begin]; + + //Turn on network stuff if needed + networkController = [[NetworkController alloc] init]; + if ([df boolForKey:@"enableSharing"]) { + [self setServerStatus:YES]; + } else if ([df boolForKey:@"useSharedPlayer"] && [df boolForKey:@"alwaysUseSharedPlayer"]) { + [self connectToServer]; + } //Setup for notification of the remote player launching or quitting [[[NSWorkspace sharedWorkspace] notificationCenter] @@ -86,18 +95,23 @@ static MainController *sharedController; userInfo:nil repeats:YES] retain]; - if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) { - [self applicationLaunched:nil]; - } else { - if ([df boolForKey:@"LaunchPlayerWithMT"]) - [self showPlayer]; - else - [self applicationTerminated:nil]; - } + NS_DURING + if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) { + [self applicationLaunched:nil]; + } else { + if ([df boolForKey:@"LaunchPlayerWithMT"]) + [self showPlayer]; + else + [self applicationTerminated:nil]; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]]; [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]]; + [networkController startRemoteServerSearch]; [NSApp deactivate]; } @@ -198,7 +212,7 @@ static MainController *sharedController; [self setupHotKeys]; if (![refreshTimer isValid]) { [refreshTimer release]; - refreshTimer = refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5 + refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(timerUpdate) userInfo:nil @@ -226,17 +240,35 @@ static MainController *sharedController; - (BOOL)songIsPlaying { - return ( ! ([[currentRemote playerStateUniqueIdentifier] isEqualToString:@"0-0"]) ); + NSString *identifier; + NS_DURING + identifier = [[self currentRemote] playerStateUniqueIdentifier]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + return ( ! ([identifier isEqualToString:@"0-0"]) ); } - (BOOL)radioIsPlaying { - return ( [currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist ); + ITMTRemotePlayerPlaylistClass class; + NS_DURING + class = [[self currentRemote] currentPlaylistClass]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + return (class == ITMTRemotePlayerRadioPlaylist ); } - (BOOL)songChanged { - return ( ! [[currentRemote playerStateUniqueIdentifier] isEqualToString:_latestSongIdentifier] ); + NSString *identifier; + NS_DURING + identifier = [[self currentRemote] playerStateUniqueIdentifier]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + return ( ! [identifier isEqualToString:_latestSongIdentifier] ); } - (NSString *)latestSongIdentifier @@ -253,17 +285,26 @@ static MainController *sharedController; - (void)timerUpdate { + if ([networkController isConnectedToServer]) { + [statusItem setMenu:[menuController menu]]; + } + if ( [self songChanged] && (timerUpdating != YES) ) { ITDebugLog(@"The song changed."); timerUpdating = YES; - latestPlaylistClass = [currentRemote currentPlaylistClass]; + + NS_DURING + latestPlaylistClass = [[self currentRemote] currentPlaylistClass]; [menuController rebuildSubmenus]; if ( [df boolForKey:@"showSongInfoOnChange"] ) { [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0]; } - [self setLatestSongIdentifier:[currentRemote playerStateUniqueIdentifier]]; + [self setLatestSongIdentifier:[[self currentRemote] playerStateUniqueIdentifier]]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER timerUpdating = NO; } @@ -272,11 +313,20 @@ static MainController *sharedController; - (void)menuClicked { ITDebugLog(@"Menu clicked."); - if ([currentRemote playerRunningState] == ITMTRemotePlayerRunning) { - [statusItem setMenu:[menuController menu]]; - } else { - [statusItem setMenu:[menuController menuForNoPlayer]]; + if ([networkController isConnectedToServer]) { + //Used the cached version + return; } + + NS_DURING + if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) { + [statusItem setMenu:[menuController menu]]; + } else { + [statusItem setMenu:[menuController menuForNoPlayer]]; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } // @@ -287,72 +337,109 @@ static MainController *sharedController; - (void)playPause { - ITMTRemotePlayerPlayingState state = [currentRemote playerPlayingState]; - ITDebugLog(@"Play/Pause toggled"); - if (state == ITMTRemotePlayerPlaying) { - [currentRemote pause]; - } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) { - [currentRemote pause]; - [currentRemote play]; - } else { - [currentRemote play]; - } + NS_DURING + ITMTRemotePlayerPlayingState state = [[self currentRemote] playerPlayingState]; + ITDebugLog(@"Play/Pause toggled"); + if (state == ITMTRemotePlayerPlaying) { + [[self currentRemote] pause]; + } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) { + [[self currentRemote] pause]; + [[self currentRemote] play]; + } else { + [[self currentRemote] play]; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + [self timerUpdate]; } - (void)nextSong { ITDebugLog(@"Going to next song."); - [currentRemote goToNextSong]; + NS_DURING + [[self currentRemote] goToNextSong]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER [self timerUpdate]; } - (void)prevSong { ITDebugLog(@"Going to previous song."); - [currentRemote goToPreviousSong]; + NS_DURING + [[self currentRemote] goToPreviousSong]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER [self timerUpdate]; } - (void)fastForward { ITDebugLog(@"Fast forwarding."); - [currentRemote forward]; + NS_DURING + [[self currentRemote] forward]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER [self timerUpdate]; } - (void)rewind { ITDebugLog(@"Rewinding."); - [currentRemote rewind]; + NS_DURING + [[self currentRemote] rewind]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER [self timerUpdate]; } - (void)selectPlaylistAtIndex:(int)index { ITDebugLog(@"Selecting playlist %i", index); - [currentRemote switchToPlaylistAtIndex:index]; + NS_DURING + [[self currentRemote] switchToPlaylistAtIndex:index]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER [self timerUpdate]; } - (void)selectSongAtIndex:(int)index { ITDebugLog(@"Selecting song %i", index); - [currentRemote switchToSongAtIndex:index]; + NS_DURING + [[self currentRemote] switchToSongAtIndex:index]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER [self timerUpdate]; } - (void)selectSongRating:(int)rating { ITDebugLog(@"Selecting song rating %i", rating); - [currentRemote setCurrentSongRating:(float)rating / 100.0]; + NS_DURING + [[self currentRemote] setCurrentSongRating:(float)rating / 100.0]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER [self timerUpdate]; } - (void)selectEQPresetAtIndex:(int)index { ITDebugLog(@"Selecting EQ preset %i", index); - [currentRemote switchToEQAtIndex:index]; + NS_DURING + [[self currentRemote] switchToEQAtIndex:index]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER [self timerUpdate]; } @@ -361,12 +448,20 @@ static MainController *sharedController; ITDebugLog(@"Beginning show player."); if ( ( playerRunningState == ITMTRemotePlayerRunning) ) { ITDebugLog(@"Showing player interface."); - [currentRemote showPrimaryInterface]; + NS_DURING + [[self currentRemote] showPrimaryInterface]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } else { ITDebugLog(@"Launching player."); - if (![[NSWorkspace sharedWorkspace] launchApplication:[currentRemote playerFullName]]) { - ITDebugLog(@"Error Launching Player"); - } + NS_DURING + if (![[NSWorkspace sharedWorkspace] launchApplication:[[self currentRemote] playerFullName]]) { + ITDebugLog(@"Error Launching Player"); + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } ITDebugLog(@"Finished show player."); } @@ -550,36 +645,62 @@ static MainController *sharedController; - (void)showCurrentTrackInfo { - ITMTRemotePlayerSource source = [currentRemote currentSource]; - NSString *title = [currentRemote currentSongTitle]; + ITMTRemotePlayerSource source; + NSString *title; NSString *album = nil; NSString *artist = nil; NSString *time = nil; NSString *track = nil; int rating = -1; + NS_DURING + source = [[self currentRemote] currentSource]; + title = [[self currentRemote] currentSongTitle]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + ITDebugLog(@"Showing track info status window."); if ( title ) { if ( [df boolForKey:@"showAlbum"] ) { - album = [currentRemote currentSongAlbum]; + NS_DURING + album = [[self currentRemote] currentSongAlbum]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } if ( [df boolForKey:@"showArtist"] ) { - artist = [currentRemote currentSongArtist]; + NS_DURING + artist = [[self currentRemote] currentSongArtist]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } if ( [df boolForKey:@"showTime"] ) { - time = [NSString stringWithFormat:@"%@: %@ / %@", + NS_DURING + time = [NSString stringWithFormat:@"%@: %@ / %@", @"Time", - [currentRemote currentSongElapsed], - [currentRemote currentSongLength]]; + [[self currentRemote] currentSongElapsed], + [[self currentRemote] currentSongLength]]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } if ( [df boolForKey:@"showTrackNumber"] ) { - int trackNo = [currentRemote currentSongTrack]; - int trackCount = [currentRemote currentAlbumTrackCount]; + int trackNo; + int trackCount; + + NS_DURING + trackNo = [[self currentRemote] currentSongTrack]; + trackCount = [[self currentRemote] currentAlbumTrackCount]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER if ( (trackNo > 0) || (trackCount > 0) ) { track = [NSString stringWithFormat:@"%@: %i %@ %i", @@ -588,7 +709,14 @@ static MainController *sharedController; } if ( [df boolForKey:@"showTrackRating"] ) { - float currentRating = [currentRemote currentSongRating]; + float currentRating; + + NS_DURING + currentRating = [[self currentRemote] currentSongRating]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + if (currentRating >= 0.0) { rating = ( currentRating * 5 ); } @@ -609,140 +737,173 @@ static MainController *sharedController; - (void)showUpcomingSongs { - int curPlaylist = [currentRemote currentPlaylistIndex]; - int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:curPlaylist]; + int numSongs; + + NS_DURING + numSongs = [[self currentRemote] numberOfSongsInPlaylistAtIndex:[[self currentRemote] currentPlaylistIndex]]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER + ITDebugLog(@"Showing upcoming songs status window."); - if (numSongs > 0) { - NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5]; - int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"]; - int curTrack = [currentRemote currentSongIndex]; - int i; - - for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) { - if (i <= numSongs) { - [songList addObject:[currentRemote songTitleAtIndex:i]]; + NS_DURING + if (numSongs > 0) { + NSMutableArray *songList = [NSMutableArray arrayWithCapacity:5]; + int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"]; + int curTrack = [[self currentRemote] currentSongIndex]; + int i; + + for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) { + if (i <= numSongs) { + [songList addObject:[[self currentRemote] songTitleAtIndex:i]]; + } } + + [statusWindowController showUpcomingSongsWindowWithTitles:songList]; + } else { + [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]]; } - - [statusWindowController showUpcomingSongsWindowWithTitles:songList]; - - } else { - [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]]; - } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - (void)incrementVolume { - float volume = [currentRemote volume]; - float dispVol = volume; - ITDebugLog(@"Incrementing volume."); - volume += 0.110; - dispVol += 0.100; + NS_DURING + float volume = [[self currentRemote] volume]; + float dispVol = volume; + ITDebugLog(@"Incrementing volume."); + volume += 0.110; + dispVol += 0.100; + + if (volume > 1.0) { + volume = 1.0; + dispVol = 1.0; + } - if (volume > 1.0) { - volume = 1.0; - dispVol = 1.0; - } - - ITDebugLog(@"Setting volume to %f", volume); - [currentRemote setVolume:volume]; - - // Show volume status window - [statusWindowController showVolumeWindowWithLevel:dispVol]; + ITDebugLog(@"Setting volume to %f", volume); + [[self currentRemote] setVolume:volume]; + + // Show volume status window + [statusWindowController showVolumeWindowWithLevel:dispVol]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - (void)decrementVolume { - float volume = [currentRemote volume]; - float dispVol = volume; - ITDebugLog(@"Decrementing volume."); - volume -= 0.090; - dispVol -= 0.100; - - if (volume < 0.0) { - volume = 0.0; - dispVol = 0.0; - } + NS_DURING + float volume = [[self currentRemote] volume]; + float dispVol = volume; + ITDebugLog(@"Decrementing volume."); + volume -= 0.090; + dispVol -= 0.100; - ITDebugLog(@"Setting volume to %f", volume); - [currentRemote setVolume:volume]; - - //Show volume status window - [statusWindowController showVolumeWindowWithLevel:dispVol]; + if (volume < 0.0) { + volume = 0.0; + dispVol = 0.0; + } + + ITDebugLog(@"Setting volume to %f", volume); + [[self currentRemote] setVolume:volume]; + + //Show volume status window + [statusWindowController showVolumeWindowWithLevel:dispVol]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - (void)incrementRating { - float rating = [currentRemote currentSongRating]; - ITDebugLog(@"Incrementing rating."); - - if ([currentRemote currentPlaylistIndex] == 0) { - ITDebugLog(@"No song playing, rating change aborted."); - return; - } - - rating += 0.2; - if (rating > 1.0) { - rating = 1.0; - } - ITDebugLog(@"Setting rating to %f", rating); - [currentRemote setCurrentSongRating:rating]; - - //Show rating status window - [statusWindowController showRatingWindowWithRating:rating]; + NS_DURING + float rating = [[self currentRemote] currentSongRating]; + ITDebugLog(@"Incrementing rating."); + + if ([[self currentRemote] currentPlaylistIndex] == 0) { + ITDebugLog(@"No song playing, rating change aborted."); + return; + } + + rating += 0.2; + if (rating > 1.0) { + rating = 1.0; + } + ITDebugLog(@"Setting rating to %f", rating); + [[self currentRemote] setCurrentSongRating:rating]; + + //Show rating status window + [statusWindowController showRatingWindowWithRating:rating]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - (void)decrementRating { - float rating = [currentRemote currentSongRating]; - ITDebugLog(@"Decrementing rating."); - - if ([currentRemote currentPlaylistIndex] == 0) { - ITDebugLog(@"No song playing, rating change aborted."); - return; - } - - rating -= 0.2; - if (rating < 0.0) { - rating = 0.0; - } - ITDebugLog(@"Setting rating to %f", rating); - [currentRemote setCurrentSongRating:rating]; - - //Show rating status window - [statusWindowController showRatingWindowWithRating:rating]; + NS_DURING + float rating = [[self currentRemote] currentSongRating]; + ITDebugLog(@"Decrementing rating."); + + if ([[self currentRemote] currentPlaylistIndex] == 0) { + ITDebugLog(@"No song playing, rating change aborted."); + return; + } + + rating -= 0.2; + if (rating < 0.0) { + rating = 0.0; + } + ITDebugLog(@"Setting rating to %f", rating); + [[self currentRemote] setCurrentSongRating:rating]; + + //Show rating status window + [statusWindowController showRatingWindowWithRating:rating]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - (void)toggleLoop { - ITMTRemotePlayerRepeatMode repeatMode = [currentRemote repeatMode]; - ITDebugLog(@"Toggling repeat mode."); - switch (repeatMode) { - case ITMTRemotePlayerRepeatOff: - repeatMode = ITMTRemotePlayerRepeatAll; - break; - case ITMTRemotePlayerRepeatAll: - repeatMode = ITMTRemotePlayerRepeatOne; - break; - case ITMTRemotePlayerRepeatOne: - repeatMode = ITMTRemotePlayerRepeatOff; - break; - } - ITDebugLog(@"Setting repeat mode to %i", repeatMode); - [currentRemote setRepeatMode:repeatMode]; - - //Show loop status window - [statusWindowController showRepeatWindowWithMode:repeatMode]; + NS_DURING + ITMTRemotePlayerRepeatMode repeatMode = [[self currentRemote] repeatMode]; + ITDebugLog(@"Toggling repeat mode."); + switch (repeatMode) { + case ITMTRemotePlayerRepeatOff: + repeatMode = ITMTRemotePlayerRepeatAll; + break; + case ITMTRemotePlayerRepeatAll: + repeatMode = ITMTRemotePlayerRepeatOne; + break; + case ITMTRemotePlayerRepeatOne: + repeatMode = ITMTRemotePlayerRepeatOff; + break; + } + ITDebugLog(@"Setting repeat mode to %i", repeatMode); + [[self currentRemote] setRepeatMode:repeatMode]; + + //Show loop status window + [statusWindowController showRepeatWindowWithMode:repeatMode]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - (void)toggleShuffle { - BOOL newShuffleEnabled = ( ! [currentRemote shuffleEnabled] ); - ITDebugLog(@"Toggling shuffle mode."); - [currentRemote setShuffleEnabled:newShuffleEnabled]; - //Show shuffle status window - ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled); - [statusWindowController showShuffleWindow:newShuffleEnabled]; + NS_DURING + BOOL newShuffleEnabled = ( ! [[self currentRemote] shuffleEnabled] ); + ITDebugLog(@"Toggling shuffle mode."); + [[self currentRemote] setShuffleEnabled:newShuffleEnabled]; + //Show shuffle status window + ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled); + [statusWindowController showShuffleWindow:newShuffleEnabled]; + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - (void)registerNowOK @@ -763,6 +924,53 @@ static MainController *sharedController; [NSApp terminate:self]; } +/*************************************************************************/ +#pragma mark - +#pragma mark NETWORK HANDLERS +/*************************************************************************/ + +- (void)setServerStatus:(BOOL)newStatus +{ + if (newStatus) { + //Turn on + [networkController setServerStatus:YES]; + } else { + //Tear down + [networkController setServerStatus:NO]; + } +} + +- (BOOL)connectToServer +{ + //Connect + if ([networkController connectToHost:[df stringForKey:@"sharedPlayerHost"]]) { + currentRemote = [networkController sharedRemote]; + [refreshTimer invalidate]; + return YES; + } else { + currentRemote = [remoteArray objectAtIndex:0]; + return NO; + } +} + +- (BOOL)disconnectFromServer +{ + //Disconnect + currentRemote = [remoteArray objectAtIndex:0]; + [networkController disconnect]; + [self timerUpdate]; + return YES; +} + +- (void)networkError:(NSException *)exception +{ + ITDebugLog(@"Remote exception thrown: %@: %@", [exception name], [exception reason]); + NSRunAlertPanel(@"Remote MenuTunes Disconnected", @"The MenuTunes server you were connected to stopped responding or quit. MenuTunes will revert back to the local player.", @"OK", nil, nil); + if ([networkController isConnectedToServer] && [self disconnectFromServer]) { + } else { + ITDebugLog(@"CRITICAL ERROR DISCONNECTING!"); + } +} /*************************************************************************/ #pragma mark - @@ -771,33 +979,41 @@ static MainController *sharedController; - (void)applicationLaunched:(NSNotification *)note { - if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) { - ITDebugLog(@"Remote application launched."); - playerRunningState = ITMTRemotePlayerRunning; - [currentRemote begin]; - [self setLatestSongIdentifier:@""]; - [self timerUpdate]; - refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5 - target:self - selector:@selector(timerUpdate) - userInfo:nil - repeats:YES] retain]; - //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil]; - [self setupHotKeys]; - } + NS_DURING + if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]]) { + ITDebugLog(@"Remote application launched."); + playerRunningState = ITMTRemotePlayerRunning; + [[self currentRemote] begin]; + [self setLatestSongIdentifier:@""]; + [self timerUpdate]; + refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5) + target:self + selector:@selector(timerUpdate) + userInfo:nil + repeats:YES] retain]; + //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil]; + [self setupHotKeys]; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } - (void)applicationTerminated:(NSNotification *)note { - if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[currentRemote playerFullName]]) { - ITDebugLog(@"Remote application terminated."); - [currentRemote halt]; - [refreshTimer invalidate]; - [refreshTimer release]; - refreshTimer = nil; - [self clearHotKeys]; - playerRunningState = ITMTRemotePlayerNotRunning; - } + NS_DURING + if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]]) { + ITDebugLog(@"Remote application terminated."); + [[self currentRemote] halt]; + [refreshTimer invalidate]; + [refreshTimer release]; + refreshTimer = nil; + [self clearHotKeys]; + playerRunningState = ITMTRemotePlayerNotRunning; + } + NS_HANDLER + [self networkError:localException]; + NS_ENDHANDLER } @@ -809,6 +1025,7 @@ static MainController *sharedController; - (void)applicationWillTerminate:(NSNotification *)note { [self clearHotKeys]; + [networkController stopRemoteServerSearch]; [[NSStatusBar systemStatusBar] removeStatusItem:statusItem]; } @@ -825,6 +1042,7 @@ static MainController *sharedController; [statusItem release]; [statusWindowController release]; [menuController release]; + [networkController release]; [super dealloc]; } diff --git a/MenuController.h b/MenuController.h index 55ae240..c9a393b 100755 --- a/MenuController.h +++ b/MenuController.h @@ -7,7 +7,6 @@ // #import -#import "ITMTRemote.h" // Internal: To be used with NSMenuItems as their tag, for use with the NSMenuValidation stuff. // Also will be used in supplying the controller with the layout to use for the MenuItems, unless @@ -45,7 +44,6 @@ typedef enum { NSMenu *_currentMenu; NSMenu *_ratingMenu, *_upcomingSongsMenu, *_eqMenu, *_playlistsMenu; //Submenus - ITMTRemote *currentRemote; int _currentPlaylist, _currentTrack; BOOL _playingRadio; } diff --git a/MenuController.m b/MenuController.m index b0ee60f..4898c09 100755 --- a/MenuController.m +++ b/MenuController.m @@ -8,6 +8,7 @@ #import "MenuController.h" #import "MainController.h" +#import "ITMTRemote.h" #import #import #import @@ -44,10 +45,16 @@ NSEnumerator *itemEnum; ITHotKey *hotKey; NSArray *hotKeys = [[ITHotKeyCenter sharedCenter] allHotKeys]; + int currentSongRating; //Get the information - _currentPlaylist = [currentRemote currentPlaylistIndex]; - _playingRadio = ([currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist); + NS_DURING + _currentPlaylist = [[[MainController sharedController] currentRemote] currentPlaylistIndex]; + _playingRadio = ([[[MainController sharedController] currentRemote] currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist); + currentSongRating = ( [[[MainController sharedController] currentRemote] currentSongRating] != -1 ); + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER ITDebugLog(@"Reset menu if required."); @@ -96,17 +103,21 @@ } ITDebugLog(@"Set \"Play\"/\"Pause\" menu item's title to correct state."); - switch ([currentRemote playerPlayingState]) { - case ITMTRemotePlayerPlaying: - [tempItem setTitle:NSLocalizedString(@"pause", @"Pause")]; - break; - case ITMTRemotePlayerRewinding: - case ITMTRemotePlayerForwarding: - [tempItem setTitle:NSLocalizedString(@"resume", @"Resume")]; - break; - default: - break; - } + NS_DURING + switch ([[[MainController sharedController] currentRemote] playerPlayingState]) { + case ITMTRemotePlayerPlaying: + [tempItem setTitle:NSLocalizedString(@"pause", @"Pause")]; + break; + case ITMTRemotePlayerRewinding: + case ITMTRemotePlayerForwarding: + [tempItem setTitle:NSLocalizedString(@"resume", @"Resume")]; + break; + default: + break; + } + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER } else if ([nextObject isEqualToString:@"nextTrack"]) { ITDebugLog(@"Add \"Next Track\" menu item."); tempItem = [menu addItemWithTitle:NSLocalizedString(@"nextTrack", @"Next Track") @@ -167,11 +178,15 @@ } } else if ([nextObject isEqualToString:@"showPlayer"]) { ITDebugLog(@"Add \"Show Player\" menu item."); - tempItem = [menu addItemWithTitle:[NSString stringWithFormat:@"%@ %@", - NSLocalizedString(@"show", @"Show"), - [[[MainController sharedController] currentRemote] playerSimpleName]] - action:@selector(performMainMenuAction:) - keyEquivalent:@""]; + NS_DURING + tempItem = [menu addItemWithTitle:[NSString stringWithFormat:@"%@ %@", + NSLocalizedString(@"show", @"Show"), + [[[MainController sharedController] currentRemote] playerSimpleName]] + action:@selector(performMainMenuAction:) + keyEquivalent:@""]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER itemEnum = [hotKeys objectEnumerator]; while ( (hotKey = [itemEnum nextObject]) ) { @@ -209,7 +224,12 @@ ITDebugLog(@"Check to see if a Track is playing..."); //Handle playing radio too if (_currentPlaylist) { - NSString *title = [currentRemote currentSongTitle]; + NSString *title; + NS_DURING + title = [[[MainController sharedController] currentRemote] currentSongTitle]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER ITDebugLog(@"A Track is Playing, Add \"Track Info\" menu items."); ITDebugLog(@"Add \"Now Playing\" menu item."); [menu addItemWithTitle:NSLocalizedString(@"nowPlaying", @"Now Playing") action:NULL keyEquivalent:@""]; @@ -222,7 +242,12 @@ if (!_playingRadio) { if ([defaults boolForKey:@"showAlbum"]) { - NSString *curAlbum = [currentRemote currentSongAlbum]; + NSString *curAlbum; + NS_DURING + curAlbum = [[[MainController sharedController] currentRemote] currentSongAlbum]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER ITDebugLog(@"Add Track Album (\"%@\") menu item.", curAlbum); if ( curAlbum ) { [menu indentItem: @@ -231,7 +256,12 @@ } if ([defaults boolForKey:@"showArtist"]) { - NSString *curArtist = [currentRemote currentSongArtist]; + NSString *curArtist; + NS_DURING + curArtist = [[[MainController sharedController] currentRemote] currentSongArtist]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER ITDebugLog(@"Add Track Artist (\"%@\") menu item.", curArtist); if ( curArtist ) { [menu indentItem: @@ -240,7 +270,12 @@ } if ([defaults boolForKey:@"showTrackNumber"]) { - int track = [currentRemote currentSongTrack]; + int track; + NS_DURING + track = [[[MainController sharedController] currentRemote] currentSongTrack]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER ITDebugLog(@"Add Track Number (\"Track %i\") menu item.", track); if ( track > 0 ) { [menu indentItem: @@ -249,37 +284,45 @@ } } - if ([defaults boolForKey:@"showTime"] && ( ([currentRemote currentSongElapsed] != nil) || ([currentRemote currentSongLength] != nil) )) { - ITDebugLog(@"Add Track Elapsed (\"%@/%@\") menu item.", [currentRemote currentSongElapsed], [currentRemote currentSongLength]); - [menu indentItem:[menu addItemWithTitle:[NSString stringWithFormat:@"%@/%@", [currentRemote currentSongElapsed], [currentRemote currentSongLength]] action:nil keyEquivalent:@""]]; - } + NS_DURING + if ([defaults boolForKey:@"showTime"] && ( ([[[MainController sharedController] currentRemote] currentSongElapsed] != nil) || ([[[MainController sharedController] currentRemote] currentSongLength] != nil) )) { + ITDebugLog(@"Add Track Elapsed (\"%@/%@\") menu item.", [[[MainController sharedController] currentRemote] currentSongElapsed], [[[MainController sharedController] currentRemote] currentSongLength]); + [menu indentItem:[menu addItemWithTitle:[NSString stringWithFormat:@"%@/%@", [[[MainController sharedController] currentRemote] currentSongElapsed], [[[MainController sharedController] currentRemote] currentSongLength]] action:nil keyEquivalent:@""]]; + } + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER if (!_playingRadio) { - if ([defaults boolForKey:@"showTrackRating"] && ( [currentRemote currentSongRating] != -1.0 )) { - NSString *string = nil; - switch ((int)([currentRemote currentSongRating] * 5)) { - case 0: - string = [NSString stringWithUTF8String:"☆☆☆☆☆"]; - break; - case 1: - string = [NSString stringWithUTF8String:"★☆☆☆☆"]; - break; - case 2: - string = [NSString stringWithUTF8String:"★★☆☆☆"]; - break; - case 3: - string = [NSString stringWithUTF8String:"★★★☆☆"]; - break; - case 4: - string = [NSString stringWithUTF8String:"★★★★☆"]; - break; - case 5: - string = [NSString stringWithUTF8String:"★★★★★"]; - break; + NS_DURING + if ([defaults boolForKey:@"showTrackRating"] && ( [[[MainController sharedController] currentRemote] currentSongRating] != -1.0 )) { + NSString *string = nil; + switch ((int)([[[MainController sharedController] currentRemote] currentSongRating] * 5)) { + case 0: + string = [NSString stringWithUTF8String:"☆☆☆☆☆"]; + break; + case 1: + string = [NSString stringWithUTF8String:"★☆☆☆☆"]; + break; + case 2: + string = [NSString stringWithUTF8String:"★★☆☆☆"]; + break; + case 3: + string = [NSString stringWithUTF8String:"★★★☆☆"]; + break; + case 4: + string = [NSString stringWithUTF8String:"★★★★☆"]; + break; + case 5: + string = [NSString stringWithUTF8String:"★★★★★"]; + break; + } + ITDebugLog(@"Add Track Rating (\"%@\") menu item.", string); + [menu indentItem:[menu addItemWithTitle:string action:nil keyEquivalent:@""]]; } - ITDebugLog(@"Add Track Rating (\"%@\") menu item.", string); - [menu indentItem:[menu addItemWithTitle:string action:nil keyEquivalent:@""]]; - } + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER } } else { ITDebugLog(@"No Track is Playing, Add \"No Song\" menu item."); @@ -308,8 +351,12 @@ while ( (tempItem = [itemEnum nextObject]) ) { [tempItem setState:NSOffState]; } - [[_eqMenu itemAtIndex:([currentRemote currentEQPresetIndex] - 1)] setState:NSOnState]; - } else if ([nextObject isEqualToString:@"songRating"] && ( [currentRemote currentSongRating] != -1 )) { + NS_DURING + [[_eqMenu itemAtIndex:([[[MainController sharedController] currentRemote] currentEQPresetIndex] - 1)] setState:NSOnState]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER + } else if ([nextObject isEqualToString:@"songRating"] && currentSongRating) { ITDebugLog(@"Add \"Song Rating\" submenu."); tempItem = [menu addItemWithTitle:NSLocalizedString(@"songRating", @"Song Rating") action:nil @@ -325,7 +372,11 @@ [tempItem setState:NSOffState]; } - [[_ratingMenu itemAtIndex:([currentRemote currentSongRating] * 5)] setState:NSOnState]; + NS_DURING + [[_ratingMenu itemAtIndex:([[[MainController sharedController] currentRemote] currentSongRating] * 5)] setState:NSOnState]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER } else if ([nextObject isEqualToString:@"upcomingSongs"]) { ITDebugLog(@"Add \"Upcoming Songs\" submenu."); tempItem = [menu addItemWithTitle:NSLocalizedString(@"upcomingSongs", @"Upcoming Songs") @@ -349,8 +400,12 @@ NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; NSMenuItem *tempItem; ITDebugLog(@"Creating menu for when player isn't running."); - ITDebugLog(@"Add \"Open %@\" menu item.", [[[MainController sharedController] currentRemote] playerSimpleName]); - tempItem = [menu addItemWithTitle:[NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"open", @"Open"), [[[MainController sharedController] currentRemote] playerSimpleName]] action:@selector(performMainMenuAction:) keyEquivalent:@""]; + NS_DURING + ITDebugLog(@"Add \"Open %@\" menu item.", [[[MainController sharedController] currentRemote] playerSimpleName]); + tempItem = [menu addItemWithTitle:[NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"open", @"Open"), [[[MainController sharedController] currentRemote] playerSimpleName]] action:@selector(performMainMenuAction:) keyEquivalent:@""]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER [tempItem setTag:MTMenuShowPlayerItem]; [tempItem setTarget:self]; ITDebugLog(@"Add a separator menu item."); @@ -376,10 +431,13 @@ { ITDebugLog(@"Rebuilding all of the submenus."); - currentRemote = [[MainController sharedController] currentRemote]; - _currentPlaylist = [currentRemote currentPlaylistIndex]; - _currentTrack = [currentRemote currentSongIndex]; - _playingRadio = ([currentRemote currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist); + NS_DURING + _currentPlaylist = [[[MainController sharedController] currentRemote] currentPlaylistIndex]; + _currentTrack = [[[MainController sharedController] currentRemote] currentSongIndex]; + _playingRadio = ([[[MainController sharedController] currentRemote] currentPlaylistClass] == ITMTRemotePlayerRadioPlaylist); + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER [_ratingMenu release]; [_upcomingSongsMenu release]; @@ -428,8 +486,13 @@ - (NSMenu *)upcomingSongsMenu { NSMenu *upcomingSongsMenu = [[NSMenu alloc] initWithTitle:@""]; - int numSongs = [currentRemote numberOfSongsInPlaylistAtIndex:_currentPlaylist]; - int numSongsInAdvance = [[NSUserDefaults standardUserDefaults] integerForKey:@"SongsInAdvance"]; + int numSongs, numSongsInAdvance = [[NSUserDefaults standardUserDefaults] integerForKey:@"SongsInAdvance"]; + + NS_DURING + numSongs = [[[MainController sharedController] currentRemote] numberOfSongsInPlaylistAtIndex:_currentPlaylist]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER ITDebugLog(@"Building \"Upcoming Songs\" menu."); @@ -439,7 +502,12 @@ for (i = _currentTrack + 1; i <= _currentTrack + numSongsInAdvance; i++) { if (i <= numSongs) { - NSString *curSong = [currentRemote songTitleAtIndex:i]; + NSString *curSong; + NS_DURING + curSong = [[[MainController sharedController] currentRemote] songTitleAtIndex:i]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER NSMenuItem *songItem; ITDebugLog(@"Adding song: %@", curSong); songItem = [upcomingSongsMenu addItemWithTitle:curSong action:@selector(performUpcomingSongsMenuAction:) keyEquivalent:@""]; @@ -458,10 +526,16 @@ - (NSMenu *)playlistsMenu { NSMenu *playlistsMenu = [[NSMenu alloc] initWithTitle:@""]; - NSArray *playlists = [currentRemote playlists]; + NSArray *playlists; NSMenuItem *tempItem; int i; + NS_DURING + playlists = [[[MainController sharedController] currentRemote] playlists]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER + ITDebugLog(@"Building \"Playlists\" menu."); for (i = 0; i < [playlists count]; i++) { @@ -482,10 +556,16 @@ - (NSMenu *)eqMenu { NSMenu *eqMenu = [[NSMenu alloc] initWithTitle:@""]; - NSArray *eqPresets = [currentRemote eqPresets]; + NSArray *eqPresets; NSMenuItem *tempItem; int i; + NS_DURING + eqPresets = [[[MainController sharedController] currentRemote] eqPresets]; + NS_HANDLER + [[MainController sharedController] networkError:localException]; + NS_ENDHANDLER + ITDebugLog(@"Building \"EQ Presets\" menu."); for (i = 0; i < [eqPresets count]; i++) { diff --git a/NetworkController.h b/NetworkController.h new file mode 100755 index 0000000..de3801b --- /dev/null +++ b/NetworkController.h @@ -0,0 +1,45 @@ +/* + * MenuTunes + * NetworkController + * Rendezvous network controller + * + * Original Author : Kent Sutherland + * Responsibility : Kent Sutherland + * + * Copyright (c) 2003 iThink Software. + * All Rights Reserved + * + */ + +#import + +#define SERVER_PORT 5712 + +@class ITMTRemote; + +@interface NetworkController : NSObject +{ + NSNetService *service; + NSNetServiceBrowser *browser; + NSMutableArray *remoteServices; + + NSConnection *serverConnection, *clientConnection; + NSSocketPort *serverPort, *clientPort; + BOOL serverOn, clientConnected, connectedToServer; + ITMTRemote *clientProxy; +} ++ (NetworkController *)sharedController; + +- (void)startRemoteServerSearch; +- (void)stopRemoteServerSearch; + +- (void)setServerStatus:(BOOL)status; +- (BOOL)connectToHost:(NSString *)host; +- (BOOL)disconnect; +- (BOOL)isServerOn; +- (BOOL)isClientConnected; +- (BOOL)isConnectedToServer; + +- (ITMTRemote *)sharedRemote; +- (NSArray *)remoteServices; +@end diff --git a/NetworkController.m b/NetworkController.m new file mode 100755 index 0000000..3ac6250 --- /dev/null +++ b/NetworkController.m @@ -0,0 +1,190 @@ +/* + * MenuTunes + * NetworkController + * Rendezvous network controller + * + * Original Author : Kent Sutherland + * Responsibility : Kent Sutherland + * + * Copyright (c) 2003 iThink Software. + * All Rights Reserved + * + */ + +#import "NetworkController.h" +#import "MainController.h" +#import "netinet/in.h" +#import "arpa/inet.h" +#import +#import +#import + +static NetworkController *sharedController; + +@implementation NetworkController + ++ (NetworkController *)sharedController +{ + return sharedController; +} + +- (id)init +{ + if ( (self = [super init]) ) { + sharedController = self; + browser = [[NSNetServiceBrowser alloc] init]; + [browser setDelegate:self]; + } + return self; +} + +- (void)dealloc +{ + [self disconnect]; + if (serverOn) { + [serverConnection invalidate]; + [serverConnection release]; + } + [clientProxy release]; + [remoteServices release]; + [browser release]; + [service stop]; + [service release]; + [super dealloc]; +} + +- (void)startRemoteServerSearch +{ + [browser searchForServicesOfType:@"_mttp._tcp." inDomain:@""]; + [remoteServices release]; + remoteServices = [[NSMutableArray alloc] init]; +} + +- (void)stopRemoteServerSearch +{ + [browser stop]; +} + +- (void)setServerStatus:(BOOL)status +{ + if (!serverOn && status) { + NSString *name = [[NSUserDefaults standardUserDefaults] stringForKey:@"sharedPlayerName"]; + //Turn on + NS_DURING + serverPort = [[NSSocketPort alloc] initWithTCPPort:SERVER_PORT]; + serverConnection = [[NSConnection alloc] initWithReceivePort:serverPort + sendPort:serverPort]; + [serverConnection setRootObject:[[MainController sharedController] currentRemote]]; + [serverConnection registerName:@"ITMTPlayerHost"]; + [serverConnection setDelegate:self]; + NS_HANDLER + ITDebugLog(@"Error starting server!"); + NS_ENDHANDLER + ITDebugLog(@"Started server."); + if (!name) { + name = @"MenuTunes Shared Player"; + } + service = [[NSNetService alloc] initWithDomain:@"" + type:@"_mttp._tcp." + name:name + port:SERVER_PORT]; + [service publish]; + serverOn = YES; + } else if (serverOn && !status && [serverConnection isValid]) { + //Turn off + [service stop]; + [serverConnection registerName:nil]; + [serverPort invalidate]; + [serverConnection invalidate]; + [serverConnection release]; + ITDebugLog(@"Stopped server."); + serverOn = NO; + } +} + +- (BOOL)connectToHost:(NSString *)host +{ + ITDebugLog(@"Connecting to host: %@", host); + NS_DURING + clientPort = [[NSSocketPort alloc] initRemoteWithTCPPort:SERVER_PORT + host:host]; + clientConnection = [[NSConnection connectionWithReceivePort:nil sendPort:clientPort] retain]; + clientProxy = [[clientConnection rootProxy] retain]; + NS_HANDLER + ITDebugLog(@"Connection to host failed: %@", host); + return NO; + NS_ENDHANDLER + [clientConnection setReplyTimeout:5]; + ITDebugLog(@"Connected to host: %@", host); + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(disconnect) name:NSConnectionDidDieNotification object:clientConnection]; + connectedToServer = YES; + return YES; +} + +- (BOOL)disconnect +{ + ITDebugLog(@"Disconnecting from host."); + connectedToServer = NO; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [clientProxy release]; + [clientConnection invalidate]; + [clientConnection release]; + return YES; +} + +- (BOOL)isServerOn +{ + return serverOn; +} + +- (BOOL)isClientConnected +{ + return clientConnected; +} + +- (BOOL)isConnectedToServer +{ + return connectedToServer; +} + +- (ITMTRemote *)sharedRemote +{ + return (ITMTRemote *)clientProxy; +} + +- (NSArray *)remoteServices +{ + return remoteServices; +} + +- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing +{ + [aNetService setDelegate:self]; + [aNetService resolve]; + ITDebugLog(@"Found service named %@.", [aNetService name]); + if (!moreComing) { + [[NSNotificationCenter defaultCenter] postNotificationName:@"ITMTFoundNetService" object:nil]; + } +} + +- (void)netServiceDidResolveAddress:(NSNetService *)sender +{ + [remoteServices addObject:[NSDictionary dictionaryWithObjectsAndKeys:[sender name], @"name", + [NSString stringWithCString:inet_ntoa((*(struct sockaddr_in*)[[[sender addresses] objectAtIndex:0] bytes]).sin_addr)], @"ip", + nil, nil]]; + ITDebugLog(@"Resolved service named %@.", [sender name]); + NSLog(@"found!"); + [[NSNotificationCenter defaultCenter] postNotificationName:@"ITMTFoundNetService" object:nil]; +} + +- (void)netServiceWillResolve:(NSNetService *)sender +{ + ITDebugLog(@"Resolving service named %@.", [sender name]); +} + +- (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict +{ + ITDebugLog(@"Error resolving service %@.", errorDict); +} + +@end diff --git a/PreferencesController.h b/PreferencesController.h index 109d9e7..b49b1d7 100755 --- a/PreferencesController.h +++ b/PreferencesController.h @@ -23,20 +23,31 @@ IBOutlet NSPopUpButton *appearanceEffectPopup; IBOutlet NSSlider *appearanceSpeedSlider; IBOutlet NSButton *artistCheckbox; + IBOutlet NSTextField *hostTextField; IBOutlet NSTableView *hotKeysTableView; IBOutlet NSButton *launchAtLoginCheckbox; IBOutlet NSButton *launchPlayerAtLaunchCheckbox; + IBOutlet NSView *manualView; IBOutlet CustomMenuTableView *menuTableView; IBOutlet NSButton *nameCheckbox; IBOutlet NSButton *ratingCheckbox; + IBOutlet NSBox *selectPlayerBox; + IBOutlet NSPanel *selectPlayerSheet; + IBOutlet NSButton *selectSharedPlayerButton; + IBOutlet NSButton *shareMenuTunesCheckbox; + IBOutlet NSButton *sharePasswordCheckbox; + IBOutlet NSTextField *sharePasswordTextField; + IBOutlet NSTableView *sharingTableView; IBOutlet NSButton *showOnChangeCheckbox; IBOutlet NSTextField *songsInAdvance; IBOutlet NSButton *trackNumberCheckbox; IBOutlet NSButton *trackTimeCheckbox; + IBOutlet NSButton *useSharedMenuTunesCheckbox; IBOutlet NSSlider *vanishDelaySlider; IBOutlet NSPopUpButton *vanishEffectPopup; IBOutlet NSSlider *vanishSpeedSlider; IBOutlet NSWindow *window; + IBOutlet NSView *zeroConfView; MainController *controller; NSUserDefaults *df; @@ -54,6 +65,7 @@ - (void)setController:(id)object; - (IBAction)changeGeneralSetting:(id)sender; +- (IBAction)changeSharingSetting:(id)sender; - (IBAction)changeStatusWindowSetting:(id)sender; - (IBAction)clearHotKey:(id)sender; - (IBAction)editHotKey:(id)sender; diff --git a/PreferencesController.m b/PreferencesController.m index b66520b..01820ab 100755 --- a/PreferencesController.m +++ b/PreferencesController.m @@ -1,5 +1,6 @@ #import "PreferencesController.h" #import "MainController.h" +#import "NetworkController.h" #import "StatusWindow.h" #import "StatusWindowController.h" #import "CustomMenuTableView.h" @@ -133,7 +134,11 @@ static PreferencesController *prefs = nil; [hotKeysTableView setDoubleAction:@selector(hotKeysTableViewDoubleClicked:)]; //Change the launch player checkbox to the proper name - [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized... + NS_DURING + [launchPlayerAtLaunchCheckbox setTitle:[NSString stringWithFormat:@"Launch %@ when MenuTunes launches", [[controller currentRemote] playerSimpleName]]]; //This isn't localized... + NS_HANDLER + [controller networkError:localException]; + NS_ENDHANDLER } [window center]; @@ -168,6 +173,84 @@ static PreferencesController *prefs = nil; [df synchronize]; } +- (IBAction)changeSharingSetting:(id)sender +{ + ITDebugLog(@"Changing sharing setting of tag %i.", [sender tag]); + if ( [sender tag] == 5010 ) { + BOOL state = SENDER_STATE; + [df setBool:state forKey:@"enableSharing"]; + //Disable/enable the use of shared player options + [useSharedMenuTunesCheckbox setEnabled:!state]; + [sharePasswordCheckbox setEnabled:!state]; + [sharePasswordTextField setEnabled:!state]; + [controller setServerStatus:state]; //Set server status + } else if ( [sender tag] == 5020 ) { + [df setBool:SENDER_STATE forKey:@"enableSharingPassword"]; + } else if ( [sender tag] == 5030 ) { + [df setObject:[sender stringValue] forKey:@"sharingPassword"]; + } else if ( [sender tag] == 5040 ) { + BOOL state = SENDER_STATE; + [df setBool:state forKey:@"useSharedPlayer"]; + //Disable/enable the use of sharing options + [shareMenuTunesCheckbox setEnabled:!state]; + [sharePasswordCheckbox setEnabled:!state]; + [sharePasswordTextField setEnabled:!state]; + + if (state) { + [controller connectToServer]; + } else { + [controller disconnectFromServer]; + } + } else if ( [sender tag] == 5050 ) { + if ([sender clickedRow] > -1) { + //Set sharedPlayerHost + [df setObject:[[[[NetworkController sharedController] remoteServices] objectAtIndex:[sender clickedRow]] objectForKey:@"ip"] forKey:@"sharedPlayerHost"]; + } + } else if ( [sender tag] == 5060 ) { + //Show selection sheet + [NSApp beginSheet:selectPlayerSheet modalForWindow:window modalDelegate:self didEndSelector:NULL contextInfo:nil]; + } else if ( [sender tag] == 5100 ) { + //Change view + if ( ([sender indexOfItem:[sender selectedItem]] == 0) && ([selectPlayerBox contentView] != zeroConfView) ) { + NSRect frame = [selectPlayerSheet frame]; + frame.origin.y -= 58; + frame.size.height = 273; + [selectPlayerSheet setFrame:frame display:YES animate:YES]; + [selectPlayerBox setContentView:zeroConfView]; + } else if ([selectPlayerBox contentView] != manualView) { + NSRect frame = [selectPlayerSheet frame]; + frame.origin.y += 58; + frame.size.height = 215; + [selectPlayerSheet setFrame:frame display:YES animate:YES]; + [selectPlayerBox setContentView:manualView]; + } + } else if ( [sender tag] == 5110 ) { + //Cancel + [NSApp endSheet:selectPlayerSheet]; + [selectPlayerSheet orderOut:nil]; + if ([selectPlayerBox contentView] == manualView) { + [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]]; + } else { + } + } else if ( [sender tag] == 5120 ) { + //OK, try to connect + [NSApp endSheet:selectPlayerSheet]; + [selectPlayerSheet orderOut:nil]; + + if (![controller connectToServer]) { + NSRunAlertPanel(@"Connection error.", @"The MenuTunes server you attempted to connect to was not responding. MenuTunes will revert back to the local player.", @"OK", nil, nil); + } else { + [useSharedMenuTunesCheckbox setState:NSOnState]; + } + + if ([selectPlayerBox contentView] == manualView) { + [df setObject:[hostTextField stringValue] forKey:@"sharedPlayerHost"]; + } else { + } + } + [df synchronize]; +} + - (IBAction)changeStatusWindowSetting:(id)sender { StatusWindow *sw = [StatusWindow sharedWindow]; @@ -520,6 +603,28 @@ static PreferencesController *prefs = nil; [vanishSpeedSlider setFloatValue:-([df floatForKey:@"statusWindowVanishSpeed"])]; [vanishDelaySlider setFloatValue:[df floatForKey:@"statusWindowVanishDelay"]]; [showOnChangeCheckbox setState:([df boolForKey:@"showSongInfoOnChange"] ? NSOnState : NSOffState)]; + + // Setup the sharing controls + if ([df boolForKey:@"enableSharing"]) { + [shareMenuTunesCheckbox setState:NSOnState]; + [useSharedMenuTunesCheckbox setEnabled:NO]; + [selectSharedPlayerButton setEnabled:NO]; + [hostTextField setEnabled:NO]; + } else if ([df boolForKey:@"useSharedPlayer"]) { + [useSharedMenuTunesCheckbox setState:NSOnState]; + [shareMenuTunesCheckbox setEnabled:NO]; + [sharePasswordCheckbox setEnabled:NO]; + [sharePasswordTextField setEnabled:NO]; + } + + [[NSNotificationCenter defaultCenter] addObserver:sharingTableView selector:@selector(reloadData) name:@"ITMTFoundNetService" object:nil]; + + [selectPlayerBox setContentView:zeroConfView]; + [sharePasswordCheckbox setState:([df boolForKey:@"enableSharingPassword"] ? NSOnState : NSOffState)]; + //[sharePasswordTextField setStringValue:@""]; //DO THIS LATER + if ([df stringForKey:@"sharedPlayerHost"]) { + [hostTextField setStringValue:[df stringForKey:@"sharedPlayerHost"]]; + } } - (IBAction)changeMenus:(id)sender @@ -582,8 +687,10 @@ static PreferencesController *prefs = nil; return [myItems count]; } else if (aTableView == allTableView) { return [availableItems count]; - } else { + } else if (aTableView == hotKeysTableView) { return [hotKeysArray count]; + } else { + return [[[NetworkController sharedController] remoteServices] count]; } } @@ -593,7 +700,13 @@ static PreferencesController *prefs = nil; NSString *object = [myItems objectAtIndex:rowIndex]; if ([[aTableColumn identifier] isEqualToString:@"name"]) { if ([object isEqualToString:@"showPlayer"]) { - return [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]]; + NSString *string; + NS_DURING + string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]]; + NS_HANDLER + [controller networkError:localException]; + NS_ENDHANDLER + return string; } return NSLocalizedString(object, @"ERROR"); } else { @@ -608,7 +721,13 @@ static PreferencesController *prefs = nil; NSString *object = [availableItems objectAtIndex:rowIndex]; if ([[aTableColumn identifier] isEqualToString:@"name"]) { if ([object isEqualToString:@"showPlayer"]) { - return [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]]; + NSString *string; + NS_DURING + string = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"show", @"Show"), [[controller currentRemote] playerSimpleName]]; + NS_HANDLER + [controller networkError:localException]; + NS_ENDHANDLER + return string; } return NSLocalizedString(object, @"ERROR"); } else { @@ -618,12 +737,18 @@ static PreferencesController *prefs = nil; return nil; } } - } else { + } else if (aTableView == hotKeysTableView) { if ([[aTableColumn identifier] isEqualToString:@"name"]) { return [hotKeyNamesArray objectAtIndex:rowIndex]; } else { return [[hotKeysDictionary objectForKey:[hotKeysArray objectAtIndex:rowIndex]] description]; } + } else { + if ([[aTableColumn identifier] isEqualToString:@"name"]) { + return [[[[NetworkController sharedController] remoteServices] objectAtIndex:rowIndex] objectForKey:@"name"]; + } else { + return @"X"; + } } } diff --git a/libValidate.a b/libValidate.a index 929ea9da5c8dbf2ba457b7420875a317f2b41275..b6ec5c19b8568332162c23c351db3d9fb64386db 100755 GIT binary patch delta 18 ZcmaE0_rPv~G`qQhiK&s1*+wNhc>q2;1&IIv delta 18 ZcmaE0_rPv~G`p#hg@v(^@kS*(c>q3b1&#m! -- 2.20.1