./0000755000175000017500000000000013623402405007300 5ustar dandan./resources/0000755000175000017500000000000013623376647011334 5ustar dandan./resources/cen64-qt.60000664000175000017500000000376713623376647013003 0ustar dandan.\" Copyright (c) 2013, Dan Hasting .\" All rights reserved. .\" .\" %%%LICENSE_START(BSD_3_CLAUSE) .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" * Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" * Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in .\" the documentation and/or other materials provided with the .\" distribution. .\" * Neither the name of the organization nor the names of its .\" contributors may be used to endorse or promote products derived .\" from this software without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS .\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT .\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS .\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE THE COPYRIGHT HOLDER OR CONTRIBUTORS .\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, .\" OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT .\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; .\" OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF .\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING .\" NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS .\" SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" %%%LICENSE_END .TH "CEN64-Qt" "6" "September 2015" "CEN64-Qt" "User Manual" .SH "NAME" CEN64-Qt \- A customizable cross-platform frontend for CEN64 .SH "SYNOPSIS" \fBcen64-qt\fR .SH "DESCRIPTION" \fBCEN64-Qt\fR is a frontend for CEN64. The CEN64 project can be found at http://cen64.com/ .TP See the README at https://www.github.com/dh4/cen64-qt for a detailed description of its features and usage. ./resources/cen64-qt.desktop0000775000175000017500000000062513623376647014300 0ustar dandan[Desktop Entry] Version=1.0 Type=Application Name=CEN64-Qt GenericName=CEN64 Frontend GenericName[ru]=Интерфейс для CEN64 Comment=A customizable cross-platform frontend for CEN64 Comment[ru]=Настраиваемый кроссплатформенный интерфейс для CEN64 Exec=cen64-qt Icon=cen64-qt Terminal=false Categories=Game;Emulator; Keywords=emulator;cen64;Nintendo;64; ./resources/README.txt0000664000175000017500000000032113623376647013030 0ustar dandanGithub Repository: https://www.github.com/dh4/cen64-qt Qt is licensed under the LGPL v2.1: https://www.gnu.org/licenses/lgpl-2.1.html Qt's source code can be viewed here: https://qt.gitorious.org/qt ./resources/other/0000755000175000017500000000000013623376647012455 5ustar dandan./resources/other/VERSION0000777000175000017500000000000613623376647013530 0ustar dandan(none)./resources/other/LICENSE0000664000175000017500000000271713623376647013473 0ustar dandanCopyright (c) 2013, Dan Hasting All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE../resources/demos/0000755000175000017500000000000013623376647012443 5ustar dandan./resources/demos/list-view.jpg0000664000175000017500000024547513623376647015113 0ustar dandanJFIF``C  !"$"$C" j !"12AQaRq#5T34BSUt$6rs%8VWbu&7CcdDFGXegv>!1Qq4AR3Sa"25Br#%bC ?y63q ^7R!'kYj([xqVQO;W*jv&>xcr2?sLcZzl;~ށSy{11xc|j{5:l;~àT^zL|O_VC6?a*}O/r?1I*^_WY/6?aj}O/r1 I*hk4ySy{I(Gk!Gjzl;~à^c=&>x5RX4m~-(tMϸo#5tݵE\41{GMstS`I*Goպ^g-ot Ŵ)t:&E#*zH[Ie-y>TlyxZq%jMstS܇zL|G0./Ӧù: OD}&>xc犖גztwxƯ_Sù:O0L|G_{5`jtw<@11YNA?洵q.-swI܅8P'x#N矰>5)^i2ݕi: >{H<Jnn5)cT3Ҫv4ߜiE[w4}'k|љMN@6d)$2i-[ӐTUnB3ND\yȅ(.,S CF@Ar<^\QU:gm]=Yy5'hy g5Ⳛ5׊h`Ay dך4k ֠kU jY 6YVwPVՐpUd*VB U^ UY@o AT.@Ud*ſtXPYIwXY4kZdŷtHȴΜB,* J3$fk]+p[<^B(a$$o9ZNy狤8GHQ cOmʓU&TYk3r}ԑwjv+8vܵL=cfm5!򷒒0(tg $Et!p2OᣨxOo?}[,qj[yl?.?An(]Dy'>Ԋxfe٣ڴf!Tʚo/4B *G)j.Iin_?󰦴F{.]T(\2rfN]IB`p\Wm;:Gal9ZCxBr@ਞ gzQ:6v,j@qRiRJT)>@@4BqGfb{|7oX*AcqU`7JU}`U^JY]`aUy*eUaUy*UUy&Wk׍Ղ5kU`My&U^I=X&Xk fiL61,eIS*W QI`ΏtJ]pɍ KP}.'+aiIjC*ekRT2di["\ - c^q@*1w-dc) g[!Xl2+N0Hd3OteFi^iPȠ!6[eO7HWKn)NHs. :V)[V4f$x>)l{@Ib Ц}*'jmmsIwb<Y#<ŧ%%drJJ ̧lJ)F< :ȹ3Щ̳ xLu6QB#c4Hi- UxyLA[r<26%$yU3ct;fc8 vC)\`2k~jxx3%&HKD娿_+W=;U6sf`ۓ3=ʘJ8!$,_$ҙz`.m%KJ.BVTmINF"[VH/ujcd2;Xy>#lwE^Zژɗ6]8u g55€\{qnSñnʷCE, ,' q*Z (=S#IË&bR<y,!XV}sC5}\-i; L;舫P ?Ng LW vz n]]D2^AqxS-=ha܉pE6$e9 ZZ #XnA*ۢٲɖꈷvTi-)ܴi(Q![Aҍn5l K1S}*Yq ة7:$ 7P whIwP 7Ѿu}>7P 7ѿIV7 7Ѿu}c}hFyU`nuUZwQۺUZV7PV7VPJ׺U@l*VՌ7V 7 5ל2=hxFhYfQMc5n=d3Xc4+4hf3@&~F~ۏmi QW?kdpKHoq*YʹsYy}p#+LO\8nz5z{w?aHBiv\DMdRȋق3UsGLSs Wc߂+1T#Üו':Q_+1>_5@Wӕ}4/N??҃Yf$?\;:2Ґ+o#N}GdN}::pvuױ;,c 8Rp@j,햨 @\mgk[m$%,2I?I57fJT}D[}"U~3x?'+)bnt7B̖}X$H}8뮼ʖrTcdI$t7o]4g薭pB8 H i8PUU3MqcNCPa#kZuTh_زjl(׍Ӿ%vn̴e 4lJ۹!XO:z&o?i2`0N2Fui8RPH?WD66TWJ5eM3!-1 q<)ͣqۓnL{UQElV=K"/8exOy||)3~z%3"Elr*uR-n O 1AHjSP.O\QD^$$CD9W S}~9Tz*o?v{uiiӦDĴ8վ 9(q_m!<+d~.IYj.nqy4pq> Nv S}~9Tz*o?/nvkbĈUC j^@VnK*RJBy dS(ݧi/oaYW{J KCp8 Z# x#4S}~9Tz*o?ۃ;fw2ecێRRj(gx}AstT=-(Jdȟ$}GŔqi칢Զ$6Qʀ_)3 詾?Ǣstu;LsFfKOGo.GR;N$%ԧv(@m}jeݺM#XͲ%Pa8RI8|Q߽!>m;(-_Ғ <UxKr[ߗ܈aK@QmYH9I89QⱤ]r{Oʂg"<+مFF 9ݨ^[mʴmէN+ZЭ УJB6$x,DV=7Kls.ܸ6Ա1mCP ْHrF4n^N3qTOY?N7\Ơ* #s0e!2}\`DA9k$怲=7G?E%*ܨ6.D!oBB%8m*Kj (pRcN_&iec)gTfQ%.!.,#($ci9?CM~1Tچ{%-λlь%ll($ahYD 37b6[ Ր[s`w4wqe\vp%c,ߜQK.oMpL 1Yw{/LmEma*N!*+°yڧ^|(MvKF b  X'p$~c,ߜQL.j-6jUirJlPۛhq>#7]{FiK~N#1`ZX&DˆBAJ0n*NQГ~sUc7g&6Z6HUzfFXZҤ:H R}$2n3ҵG!f< B2y$,cߞNK͢Ͳ]`⅔C1I#<>5GoIɏ5=R]kͰ&AC3~6T!¼`[~cc2mы v4Żz1xmSJHvJNqnVH+#YLe0 64o-)).SK[i`?z ocS~{UC qQr MqKKk* 4wIЬͿIv %E.bs_?=7TEբa.\[Z#ʒ% #t6N!)&k={twCH]~X>BR*RB@iy qn';R06ߞG?QK AM~1Tz oc% ĈSӇ+BҬcXWru`{󛪆u״Y3]նci6cmq`d8(X øzi gy"g҇JV 8)<ʞ-~J=]~Ji Wo#OCiiH}1?!sc ֟/T;QEZZ٭M[b]bk6Oկ?fY5ouGu PGW8#褨Ql9pRAgjy,+šPFG4jy֗ۑBl$FRk RחF܎(,'U1^ T1* gT @>ڐGl$'+Fi4- MJc8}1H6yde#RՌ$%#TNWjگ̫-HGd3ˡNq{j(s;ZݴZ A bl7.}:JRF1svoG(iZZ]uֵ8|>Gd+9IܞȬ4uK51ߔ[ )l3IR4YQ]pJ64V<֌wnP mnF['MɼDu1ӽ :CI9HHiwC-jL}*Y TХ2 ˍV±rHJgO"sBRzPzLc, pqKvW&twkd5jS2O"PLvTXb^e9s"$ɪ=7`)IkP\ӇgZ;^[cAhWn̴'Ȩ +٭˨ܻ[nkDw=u`'yvMSKjdΊm7vzILgR]Dp$+qP KQ_\M{IG@SS1ٔg (q2RSnGRZ;+.-nӟ3.io%%J'te'u.)Pk|y$I1Zv ·]m(͊{g rz8>jyY>˒v3KP$Px8#vרo3/v[[:b&Nx2RRT‛KQ$iMIiLa**J]=Eh]!V%>XYʞ98>ԃB Uó|FoWxOtMՠaI$GZ+;n򝧚`f1tfvB$:'!)K``Ggzyw9#R~[,\ a/V0ʝBPsC!A1֯dhYgH7PsH5!I%(Im N'h:Jۭ aաau'>@OJ>^ufw]Asi)!~;q -\TTk}.[Z+Y@KUjpv2B4VLݲa mp8>"vell_/D%c{=6zĺܲ.eeKP9%{:)vcj+嵋LmLJC) xA8$TX%?f:|[f$EHr_@WG- i;:f%?gKlNZ_DBZR )8 =\hI(hL[2eVih0i]hG/OnoSDž1mnm94㒕f.|}FC.[nk@*<% ~ qtǷ]-qbA0 Ԯ9 ṁ.W{LV56i(yF=ۧ\x]Sl[nb.Nҏ^nȿƴu rRP !޳*S5.&q'XݺY !mZzejA3,!t]rl.#HV->-ͶwrBrh=[pp^7*%UպˍJRg;N9*$k SޘmQn婩iP_Cu[YN>~'Vbܫ^qp::ؙ [iRtLs+Rddh-ny/9OS{-H$dNIfO dYxByo,  Yیd:pq Ij-뱚돺)Z%綠!/$/񌤑hЃB0#**Ƃ1,Zm3n¶J[\N!Cn TjWE٭;+za-ec-(eI\nͬ~&ʸf䄦 J.()M!JpBrI%Ӳpȳ=--j2%V)(l*Q8N2s13Qb 2M6 `$q[((UQ=-ĵ|J~E@ѭnkou-k?تV{͑[ ŵz ڎv F95 s}EM~Z]FtJ<_b~B>^v*)Xgeտ?+Q3_K>jF9mRyu *CIײ5RaXg*R[Б[|Ea|3Xk)O hOV:#!I9nÎ9[gr=]ACmT]rWN9F-li %JB-zF.9Ο% rJ 9 [R+ڬi\7:j!XG !¸l|1X/F'}Ry898S$rMx$YiR[BkUM %-'y'μ(TrkF&۱tcʡHW{v0OUWOe7'NχN%-eⰲ2q}4Ӹ TWmlJPil9#('`J=.'ZwJOGB[e*%AVROСi?}ߥ.BQ(G{II u$l瑬kѕ$/IHRٰ:^}Ը#AU[쎧z1CiEV61VBqii[PMzN{kyqz=0!5ܥlx \=<׭uf֥&CHJV9^;:81ma#ŴWj|'CM}ٌ DWO\ 6#n'D-6$~y1U-ƚڍ9f8qiA/j3 \;/a5ieB%S_Y0( HZG ;y-#OoȤ֕BB%pyݍV߬i/U\ 9lmQ /R^nd +P8"p{GrC]\86UDpAj;|).i|pZli\,i'( $21Pq[=Zfʩ]sšaHKhm R2sZ}ͭuej9աa.; (7eI>@Z9e-W;ȗiyOApC @WdR4>uP+Hm8$)8W`Bq&}[{ӯ\u!Vd>D))RP*r75_2̉SY4rpm.ZԕDRVYyk˚/6kmḨ%-H-H <$Ht1oy  v1A95Wvu7u^=x~/إZIؓ+ڬ]F=^gNtOȷ1츭ϐY >OrN}MMZݾa}cr%m)*I :@Fr zP@uɲv,-$]Vӹ `<+S= >ABeZRO$[ti^,Zune$J+Z^q@)JQ%Ci\{҈H(<^!j+K-!@ 8ydsgӚf˧Ɣ,9!ǔ[eNVpIGW星1O- k&_4= }##'~*P<t@zBd)>aHOB;E\~b5](87zkMYV#8+۞OOD툋S#h+JyWC =L.X0v8:|A98>ݨtuHelr JISkzsAeݝhW$7"dh[!_4x>^>+vsmYsf騬qlJ >l틷M~nĒm< vI'2)`sqWrnER@ I$cJ jC]J@q _y$y5ikn8Às{VtbG$zzv-' eVO{/?BŷoCk:%aNq*u&ԑrfDRRmա(ʱ@{IeX} $XM:MUzr>#!fU֓wX(7jgNӿ!Ϛ1c\y&iy^>]Ej{+ɑ IT\p yN9&V}C&vep n0^IK_ %hd?d_RtjEFom<@iLTI'$yi6bJ-T˗" ;s1tm\Э.n77<Ž.9XPsvvsQ{S?\^ɹȔ%l8YmL䖂A)HhiMGqGieAdy9 wOɽ|b=ud"!O,8F65%S߬Nv4lh'1 FWT% |zX#RK[[CM Q(JQT<7܀:GP;eCaeEM&tF R W:s&~9|pzA^s.rM._-db;LK=]R6`{DD)Sm]"f wt)$)#%'iHdƄmOiViKJC|6T1<&6R] pqT/Wa9Ml-*q`I$uN}SPV\ܐf2 QtGkg'm$ tė&GAy~}<by~nb m~cqP, p<ϲ)z+9li%T-dZY[hKP-4ܒJ>3PET8vFK Io2Qyhoh-Rr Gq^Jih! MY!0[GUhPZT'>DHu#zby}L;%,w8gUTZf sxv:a$)*9`+Qn7^ҭVtv]B D܄6d]]=D ldIswKlWPȎؔ-EsҺblbsӖy#sBmƚW< T_4hWwWŽ[7ryS6$d87"RHj311--ˆ!Y>t U4zLZU,FMK>jJ6]4WKda\qL9AP;|f,Sn,7(݊9>GNTQ@QEQEEPQ@QEQEEPQ@QEQEEPQ@Wc;]1ՋO??b=Z-$zOsϏSk_*7utSOjy}p#+LWo#OCi nzڊ(Ц|{&ϺHǕKר4t] s eA/؜c|sO);uR?]Cً*'QnkNVHlˣq#Q;PlFn+7VpB:P~ ~It9av) Dfk ^vf*ϭ'o쥨K-Ɠd!bCSVh3^$a-x#<e`[2XT܎^Ջuf*vBք #%;ǟ48kН*|XfKQ} J*V=>՚}'tf4),){LvD̍/nq^۫ O$y53JGF=+~ &qAWVZnd/)*wU6~-pn͐a|y8NĴ:|ɽkiSyEi? ݃«l8%NIIJI8tm1ZXq_MZ^N6dy.ŰtU^=hBUvН}[6N $coaq=BBs'J* cq3uCKc>hhkOzeJ UScg)#-A<AoGg-/ac4LHձDqrUTV,+m.4W#/OHb`7ZΩLC.aoHҩsCm{;}YkhbEjIqoG;Vs^.S?pbwzuE=ja%y 0aĬx"4X+gh#j=Dƞ=ýA NW@`;F7\4&wDvsٮ IZ!~ާ[JrWOϖ)P}3E[%܄oS-F\KmڜAV|ʮ4-,M3W W(+f߂^INS7tԲvDnՇvX{7c:m%#!̹IdruU-PqЗ2 Q /AE==s_REjˋ"5@d %,d %y֛P2a<ŷ$[ؕ. C+Rq)*/ץ4Զ"<\|+aK+Rѓ@mOϳ5aej]i:F`_ iR܄e+^R* !>,Jπ,iƣӑ}"&Z-TG/Nn>tEU5jkh,vvXEE9)eA%R,B((( (((( (((( (((( ({jUtǫV'm_s k\#i?=?EM~F4T O_?iH}1]>y'.~aj(KB5~M\_4fY5ou$AhyǤ4I%B2SG ~\ ֢'[Pzu?tBT689ܤ uH}]-N"YF\>^\QTH/3Utdv?uQ I|iTEkrIR}YRZɑflDžr8}AIkRiCR Q*V*Wm.㲴1`rc0 z1'c4ݨ_M^PաmaPCOvdW4YԄ]HH$XH.kqл}`9RXvp0Aj-*w`󔹋heBl( c PV/AU| Dz.մkd:VwBva|iNܠMBB㐭a_̺zYbYRNjQPזGSϿΝ w:;Nve -()PRT*7Gc%d6E_d NNdQ_f:M W^>At%Fv̜'!^ۻ1>XoUj#1@J+Y϶6Tx#ndF]q{>g^9H{jWOWP:9*r )e7?_T4}[ev mN dgjG)m[Hq/9*4c8gg-t+I;7A7OJ!)*QOt`#~4q0R )A#]O.G_X7JtMZ#B-~wݿvݷzM6AiL(t&H3 s lqYO=IJvU[yg` Ev.g~6Ϝ)U@;Jk{u@$-=2w(:Q\_=ݴݶ"R;g=<9),me$b,mUbytiJ[ʓ +$ ԫjvecvqz5sg1*llQ fI^[( "+un͖<]e 5Ǜvx9-CB"cɝV҂Jѝ W99պU.iZ rL V*uEܥ^Cgq  ؒ0q2v{mVQzc(FXIRԶxsvPڤgʀ^Z}۰wH\˜cF?p>5wdȍo–`ڤ4Z'se@r|C>c\ЎHm5}- lF+ U$Ǐ:n7/.űcnMUSOOǗ ekp;zw~k<~2ЅC.7+;Uw+r/9![>QܡVۍjKq[YgyTNCK9ŜׁgNց!t(8* sm*J7WY{%#e95.j0\.-N` tvYmfxܒ% HBBy^6ۯhwoO:՞*kr Eΰޝ)aV@'q2t=X|{3tTҥ2(q-₺*$)H 8 rs!C"IhY4u [7}o8ۿ;s1Xnvi7~OGJqso3T}ԯNe{ 4;=VĀZ8B Q3,Ytwquͼd2]Z%@ڒH4 ӦGw5nHmpGKxsg O Aaܣ''Jw!ײp6'Spi%跗6 \Kki+ᒕ'$!^oֺ;Zȵ얻tY6LCy ,0Iq=e:RRrYi{} ;T|G  قD4kM"&qُ-JJ )D/ $gRfV5wKfrѰQvC%nHiдn )rrH#p"cwf"nfnJB^p>G{)Z_P͑ |*\wel"B @ZAH*O>\}W}4; [K uTj6:yi*LYߘ]vqjkp~ڒRĄ>tD7&163Sq:!%Rq*Ѵ}JIԢPaLe·V@K`<`N&_t\˺.h)mL ^zQͼ^Z6~CW XOq¸] .Ρg)i࢒ ȭz] KuzΖKY Fwx|hZy)#k&s=.ovm陏0Ӎjh6SRRH x4ަٳ$}1\\%m<|)TrT1{+WͭSW1s;O>Н[da|ޘLbu(!{*8)9kXY.tːu$7%+BEx5:Sz%,H [ޟaYN4䲶s!hQ:qk5lޕdjl]#&e3wysNuXj姭Y]5߉" JҼxm< F ( ((( 󶯹?b=Z;jf*cխrlCx?8OOS_*ڇWE=~!?ҿ4v4>GW星1O- o._4@fͫO?+|R$qYG$bFi(LVp:Ipưsqf7i +c99e!➯Awz`x6gyPNkt]RVv=4|(R:H4i*; T7Җ8{ؑ ֢2My4 !N9W榩!®Nt Jv,qnmjAmJY^ ]qЄ UO.Gñb7dv Ew'؄@ʔVFT2985ޘe> ˬ;H Rs#UvZɡ/K"i6M%D|d6R# (tH;>Lu;PwlrLAs}9$KAԥs$fsWD.Iv# >*[EVIR-Ss>>f!VZ[xt҂)@9トPڵ)^4k䫦Dwk; u%J)_I iG苭WE~eWRW= `BÚд%r޹n=2]Q(lTA'rRJk.>e""!2KN!,uJrO5nf˵k$8%y(>`iOhY6=`uTvCv2fldFZ^q-20R r8Ƞ,]7m,,>Β(u8܂gG4_iImf""<.GN2n82 {3v#ޟˋqLìYq PH'hN3i̵U^mm`āqg$u=)(P(iG!xτ&2VurY =8{TIcqvPhIjuIKl8{Tv'+nӬ7NePaCސQ%+}KT(I[MLozb!ri$-[B@Mj\/U; uj[! @*Z ITbmudF\?D`5,o(-qvӎӷ+5nr'mbiHRIH)*¶ٴ6zٌJ(ONW6.ƻTYv$}RqcJGJ2U(R3*=$[&\_=aHPahK-E W`yeG{ 6tl` yOVǻ9NTc\0[C_\w6̶pHB<) ݀լK) CN)9 ^@"14bsμ<CxKٵէj+®84eGK\4\Г;-O͍8p'@4ˣ6eۚ2[&=*4'-~.D$2pT݄7V6KKqMܭV[c"%E@!.e }®H!CrZWͲk7;]BT)-KPJ-J$IDv[*u;R'UЂ^y82pyNt'w +cMhWE@ 67-nv:nC;rd%[]KjZA HA8*jhSȾ[f!O]2d8̹KKJډZFxW+dvgL]cv PcԐ€}^躹lU@toS6%JޜeC%@:Ot NEcTڒ6Yuu)N4:jZv+rRTSdpi~hDnD Tü4JՀ/DZDu S=_O\4wl~s%N8]Rlպ2R$0u ]QGMkyBrR8hKeŋl^kƵN8J{amK@VVݟj/vhT0Hu*eqa 6B![R>lg,ҝVxq$>zaU9rֺf]Ѭ\xHqQCԝJU#oVص +T.($6^ upl'#ҽNOF)1%?zÁ2C(PA~z/<6HYl+Q䔺BDd!CAϪ 4ݲm3gܔ)enK%ʉ PGSe]SREz+$X{k83~q1h:Xj)+d?Z%0j,;isQꨨ)JŷTLzMWCNGd6`e8!Y8h ֚npTy ׊"#j)BZRIJVRJZ^-M[P :[J؍T`' DSкVPCs`[VR&:b/bi;_ Q%?q~Oj]]w+iKZKMJUwGso d[$;+T0/۵ӭ !a.tִc%Y9 LFM;o/.6}=I/(o9  ZEPQ@W}ϏA3]Չ_1Ukdw'hůxFZAV:)5]>y+!?ҿ4ь7Z|PEUihSfYuouj;lˢ#)VQ$c$vAw/٫]]C\bT./iK\YmGBSg. ssXTn ؗjWH$:?fv_4yZjk]'JԽJH < '/eOKm|Cuj QFV]n)#=reM % >Zi6ʚ PJXB?lZI~ZhDIKҐ(#1S9)XmY( F[sxϴnUfdVS߉5{ =w,ftCcy  .im@vk{b`9+sLsRM&Tmi,p3K JMR2LBqYSK)8k\u6r#e\~K>5}K=B ǴWYƍ*ķcC!lBlxT}ލ -4֓ڧb>)@V.Ү3&2%DO5M6<+}ƹE'2r<\RtT֊yK&Xlާ[ѱT%I:E6|GeW4g@ێ8jZdӲtM-rm+[@-)wY`ne2\Zs~  {jGWo.{Z.+\J' >櫌tjU*_H)%* XYrs׌+^Wob?T Woa7+SUOwm(2w086)sv%Ch~~`& Ve%$)e8hF3>kFЧW6u{]%j G9s[]bRXBBJQ  ]E@&HGwyHK{ BrqeڅǼ+pr:{ӬDKJ}t8:QȜ"ڟyw)v؁rewxd!h߹agv%[B8M.g:̗ӍXv@#>i'#8 ;nbhy&-/7K]ԧʙeIeū %*J$ɠ&SF,ۖ LB:-iz=22N#8!m-,7Ȭ?1=L~(OxAAWT>b޺~ޞuco>^\ Rۻ/IF;)$8[TJR 2gnDzԗEqbR VcNFtL.o̕w-3]yހ8R2<dsO8 Ot$QuK04VP\e$c Iov?'?j{ؤwniogsϖ15u6 mC)[g:3TTVV2-Iܔ $f7wbl;#6FS crpm*ARVI X]Kpeb; >I17qo%%RJB Rp2O9oC/6o ?2-P졕>@)]U)O @ $nfg[.qUbRZRp%\)$ |k̎-lH6mWu7kzKM-e uPr@HE4^8]-jPl1pZ-D -INRw3g1.[\Ɲvl-:ǝ w跋AK=O)pz"T񄑜Rε,M"\>a]J[JY$9,q5T8$[eXmwxW1>; #i[%7,:%̂x TA:nɹArp=D~J۔W9s#ա;aL-{XؽMv$VCqAcć]4t߻8ГR5$y+X8!ُ龷~1=^gHIlo9xr1K+}-e[Cė#P\ͅ$ (|[7hUdKj#P.GA%-%o*QPHINHӭ^7(qہrjNāquiO6Taec!*JBj]ث Fb6(yzXٍ>GIv[nӷ#5`TX}z ;+*!*TI JJAQ p{pس&6mhe;BԤ'BI7Ne \Y14޷ƐnqMZVNvWvc3,n~-&,l2P;VlX\ܶɷ,CIY(xEN/T R<((;1/A`CZ8<qvrYAʱ\.I+Skmm^JP }^M.ٴ9hlɬDJ\q2 '  IDeYV@̲BlPmq;֐|AyM#6n1Z CkB\SBBpr1v/kZnLFH;َ䤭HZGx R @%$xΙݞ>_gU[zYow=,1㘂;7oGBՋ^6ږ,Zq2<&h(( (kf*cի?b=Z-$OS_* z?9utSOjy}p#+LWo#OCi nzڊ(Ч>AV?+=v %I %G.ys]~At AH,zd-*a#>W?迱[Ucвc~G'&def!Z=Xٜmm$)\sO' M6}h;PXePbD%xƬ/P> ew jTk\G|+$};MR<h88 -iר`\(yǗ_`J`{H0Lbdm} s1kURH9؉tt9?&??ynӱD>Y?*8D;xvGO*n8mťD`:Y7Y@]L>9bcsCc'㩲\).m uej ''* (tTqVM]E$;W榭 gi9kD@XJuie=>b%hʬgp:m3vmġi*Jv6s}{^VkGBc[Je˷V:Pȹ[DtLJLFZX TSA>bPuׁy<t[_.VD]$e[BN|p6+yk{-λmL8)l INH9qJ-BF+'o).BRC IJvۼs~JD6iQ ”Od ?]ND;&V2L :I0Wχ9"ɏtL}Dqc>V&6{VBCk†MXZ6;pøFr+mVŤX>ÃP}GٻnMppm;J mHZw$Idy.$`\t$Ba(%N 嶒 7TI $Ԏ%g~޶#0򘈷RZxb=yƟ}v_[9& _EKzKi\p}I >ԂfW-Wk̅[y=ēW В,-kkw+d9wwiC0lN:`%0뒵h[2Y ~6uێ\r\'ۏen+pivR#:ia)j-%[TA pStcLO|IrgouƔoRT[ OSo:C4TuyڰH8#u.:s.[F-[]=. Q1^g%=f+shX (q4s^;^eYQ{d,Q4Ֆșo- Qs⑆(|^zfċh/7s<0w)H AJ) C|ʟ{ݖc[脘CpU#Ht!i`˘ !ΖmTʉJ3_ DZDnDҋ-b+uP_y89β~teĎ1'(>BҤpAn߬o-Q'Iޖۭ`b~԰̤-Tḙ] yS).dw'7p;$˺tGks$%{H׳ż76mpOf#kZIq.$' vd} c.]lnңiu-RBAI7ה\p 5pm Gqq.6[R++ϭvkFnWi..L"CRzI 9)݌I n:wXgݶE-)vIHž͵˺nonFQHeMm9‡,cSSZ_n5T&;+*q%(z-@ +hmw^ޮ׉CTIi)*Rrh+$%<9KEPQ@W}~A3^1Յg1UkdwOS_* {:utSOjy}p#+LWo#OCi nzڊ(((( (((( (((( ((.- RP)G&Wm˨JZJV P+lDZgiKf-Ე])Ptx>j qZ$ur8.6NKcmtp(20:{sDv)֑(\(3ض.d7@ <^)띸HrTDDuEէ-!JVw (s#K95 j`[lRҖ>Ѹ8 ֚Kˆ5D H\gJЖ/@Q`g)9/e͑k$N(2~S@!%KqJQ g9PޖcAw;uks>ss|#w!v-io8vD6nFdǢ %JY!G+;qsHg4Ϲ[@>mnTX憤0r`S.ی y HА% 02s@f( (((( (((( (((( (((( (lfjcի 1??f=Z-;?APM'Ώ"vPUzi Wo#OC_c_by}p#+LO\u>F*~ErW~( dY( 1??f=Z{m~k\#}?b*yk@o:?ZAV:)5]>y+!?ҿ4ь7Z|PEUihQEQEEPQ@QEQEEPQ@QEQEEPQ@QEQEEP^QIy)f3 4\zC{P.y]iK}Omݷ&B$+xI̊Bi+67Z6%:)A_#89C/Ǵ]]Y4̞."LgGj%#ʄ\]d:63]2„/ E?W-vqt -f.e5"3[z/YG#*ô}9hR%\}crӠKia fs'`3wXOE9hgjA d\ڬ5o}Ù2DoJW݌ԅ4@#ܒq]!v5Xl쩘0ږVR$KMvݕ\;GLش%!kMobĸ RДR R8E>/j&oݳƞ(m:Vù$xrZ vDHzmPFhjdz8;~]Ud7'LwQ^e B $l(WF)gtƝri{GSTmĤ,d2T*qC` q^d*:4[-Ndw>}>4}:OFyؗsI>@í>eĸӉ Br2>\f'WGeGZKzSin&xXVԩF'pxVf{ MtOCk-RAYIxڬc&Xz+lڦs%l]y< K-m*$.='9j%7D I\ ԗ":R8-*,P 7nNsc\tv?^8 v<H5\\ )zDRߞVPIx,tú+NkmF.zL?V J*miw00@KOpVȾ orJZw`. F8n&i[*keCrʓqikPifL5-2$~5Ki 4.֕ٳܐ|!`nB^hmiVmkԆb1/] O~'"gErki KJ'^b[NK[pl=%DAۀB # }}(nj@>x?( .͵ j n'Rr-[{ʊZ* !9jۻ7jͦ/zMκJ)lW9 ; =v4tCoDgzؔ+vgSi]vuW\."uaV6%o%.'j HQ {??`~T$t\E{'N ]5tl.IZ^Yqj `0 봽%j_Zwt97+`#c( +J; *Ek g}vk R3%lIt!l o9VH83 .W ݑ̇>c" u84`+V=EP(( ({n~j?UVjcխrlI9?AP-'?b*{k[P觯֟v4>GW昮FtJ<?F0iCQUQEEPQ@QEQEEPQ@QEQEEPQ@QEQEEPQ@D{OKl REuKr$ߎ+ぐA2Et`z>٨`nW]Me[/W !) r8_|kg6u͙˘ J'[-ۊQmkYv-$خ֡(N <R*۟d:joiOϽøKm-NgN4%!;]Fܩ$% yfc}$KjS\WnDZJ}v,Rě0WA.INoNJANtJ؝g';j FvӤuiؐgI.)hsLO+ TozRݡH\L( G9I=j+>Qkp#M.XYrwPЮw rpMŷޛY aJ#0ps@X'AZr$j9I)Nm R>|^읣ڭ䫄vND֌7*H ܕxpõVz7 {\f&RNp l +qNmŦPڍNЭ.v5w%^2P’H^?v$KHj@$e.lY\^솢"K %KJvy>8NZ`dȷ\UoK\fR\[V6[+Ig@6fLlvN\[DFSqJԙ!kJ2l``Dv]t] jݤ|,{rs\ysii\+7ez:Rn>#z'Ydǻ٤N51ʐL%ah@NҤ`$ 7irK$g4r3L!2Zn[(s)Rl!\ PESm'n̹D騬k} BE'P#cU;!S\=-d@0dwAR:);)%X0²(R L^t}ԡIu)dgqJI*#yN . e\=nۓ)IqQ D+2 HG4)~C-Oԑrd/6r[>` 1*aa춆m!BFb}j+Ӟ~ݥIWY!JP(<#d`yUxе3vM/[fmUGґ axm 0 s2xZi+s谲|%*Hݏ8JiK.6ٸCXڼN :q.(4(-`4))=gNޮuowz舭`g=j>:J̶ V6(c qQ-'؆j8WMGf[髇xD!'hvE"ݜ.΀.3Zbh ۃ(AݟYڢ*{uzbq >d+=v}f6c>C'=Lq XqiBH)Hۄf|uEPQ@QEQEqw8~ǫVm߼ҳUkdwzO'S_*"ֿPUzi Wo#OCiiH}1?!sc ֟/T;QEZZQ@QEQEEPQ@QEQEEPQ@QEQEEPQ@QEQE~MkaՒF)!Ҕx)$1>9Cub2DmmJZZB R $c{=C̛h6̛`]L R2Z A!vS<`iQ9rOM˚ץMΥiR^c.$4R$ʀ\Ǵ -Aj[$%Go lß];/òh,hC [*I y*ݓkޠAsngn%<!$ ` ʀ \{%ӓ]I3nLz4dkR0Rr~ZX:U>m="cGd($=]VޢBl=3:SJ-.%m )+ nJ%}NT\J L%@ SF} e:vrd鄿mqktڕBӱh8ZBA_$76ܻHbImE;'y'8W456*ؖ|t%qlڍ)_iQ 0VkwCMV\ ӫZWA I+۴ˍx6OFvgalw[G5GN6zx57طǥAGIKiXCa'!)I>h+}騷2|KEJL&%UJvJ* *)+c=m6j&] VG]orZ+Vx>^*@V=ʣ]W+ x\xR,73'rV!$ƥ:Em`, V@4E ;Ty|IK]["BΣ) Rg5ېE 1uU;)[ I[[+ +XʰvJ"ik,r<`t-.4UڣhqҬ~p3v)*Jq=:\e ^pdd}xigG𞑥Eթ(++cSW.~zyZw&WܴqL HRRYKW=X}K2wmc 4 VnWH#vTgd_D+ZuW4.r%}gl]{_^UtNlin jƻ( NyWE[" ˕*7GnhOj %a@\cgXt*ޥPsGg3Z=}]ܙխZB֚l}7Fj^L;TꯐmRHH ^ >`ҩǃj 樍n1-H qS0vڧ%Xu@=shZG\i]X[Qw$bC2Ē|H5-Œ܆RR3魸lNxnIGn'},ZJڝ,ie\qG J.lOSYǦ~!ԇP"1[^g>Ѵ܊iˊVߊxUr{[(%z8MɝmշYjzG[OE}zD)xy,G?lpxEzDs oAj+IH}BӎwFhGFU%~K;cw PY߀|P$~Vg4]8ݫ/Ia#~#>6k#ɍ)ϴ. Rh +ܣD1.KrrzNլTT0Tr8^.DtDtGߝixj yXOR>Ui)sn YCq; H` s4f~ ɩep<{VI[JwiFPS|j5j/D""VGLӱIKhuxwhQXQtV!,6;O=qrsMq6،Q$A8Қ^Nm7m1S^gŊiC,!`B 7m4(Ju+m:q8ZBS*)Ff݁f»பT H *-n_&nt Jpm!,vZ]JHQWj~yk>-䴟@m=z,8'1(4v^ cR0,m7 7PM~'`[VxP<wƟuk _<|]j+r p9׳⧼2)8O[{ h8XskhMw5W̗oг*Rȹ\&Zc\q ڐvuSyh]$G#yWCF@ypˉMegfoBG,R&b:# Fʚr_W\)V4yRKQj_~ڒjZ&nxyndKS1V GH{q띩-&s1cq{['$-$X3q)bd[h}cHX}-)ZÁCn gyR=!{:vŬ/M軬Fiʔ]H}}*]WckPFtXqq }ǠS{s_2LۘAb) |bͿJ^%]!*HJ@G8ϗ¦7vcQƋhaŖ<\jvg6VHƃJ!NmJR>;NѺPhKs,!9ʏ|*ݧ7Y$blQ=T.EKGN7X-R5]pMܵJQk{;C.M*p!e>~SU0\6[F] H@-HhD-ݐ2<H M&)FRs*xy_w1WN;]mx]CuaYbp=S-+yHx(AWizD[NP܂P@˂Gʦ݌W6VBK#:U} ONTR>߹z^8ʀߑ7MJߍ$07,Yъt^%T9NBAS'vS~ʢmKuT)VGw}7ÎxR 9^lņlh 4 PӈSm)I` ;psvٴKBHRrd1D@RI8UI<|{Z2Ʈ j( Skm*SL x ~9sg:]d>FV[ldsOZmLVM/xK tAkv3HOs}e۳/Y-.N dDQm }SV0Ary]M\gDZO~LC嵢: DZ BrRA±'Ni[ڏaݷpCqHH;qFއm} 6@Iq;\ m<+85zcYkR[XֳmѦvfȸHui_E* VKI)lT厩Ggz$nL{!Go򀬌yTY,"Pi,+1NQ#nс 3{Pjhś_cLw4 V` @p\,W%ݙ[rPN-;2]m7y'AnIv\;@rc/w¤9-'$1H:e 74l:!6BRN@xޞ46=3!r=|ͨlg+W5ݤշ^Mwobj `yC[ ˮ9#n26qZ6KVi7-7ҦKJ0,@I})* ǘHMJbS_,oBQ nBmJG`m?a􎠔e .qTx :NE^KV'`HDCu!NH)!)YMJw=j/OH2⭓Lq *c:\”$;Ӎ9*>gn!ؔ[RN@RyM[16ӧvEhjm(A|<PWSmY\`;5l{XHyJQV $C nӒyi5(qM$|>@j;֒j6uneIe3%"$w1+4 21+aMoLL@R\rÀ)^%) uA^4X޵ +?Lm"c,&Z$(R|$c=$^ɝ o4WKrvBxrXiג* D@j ± B2] _\ o[N3珅'Y_ɽDDބ^GrNv=M~گ6^eDImug)9SpR23]" {^s6'q̟gCR. S}Qz#P1|j*L,/-s6ZR[6PN{'gmY5W[mO1uNJwz;ՐPbү8ӏi3f7tiK(ciOHqEc䎔M1e !6pkzIm (IHm=QBliַ-8jR1TTIOb mB[7 JTD5 P &(4ÉKjV#6VaKZTO$[((( (((( (pWYOpWYklw:OGϟEXPU}uUkZSO5]>y+!?ҿ5820iCQUQE\]iO%?hlh>kk+|6LvjBRuk+ #=]5/3n^Un ;DZuaJë76VS.JA A"*:տC^C'jdgj_Wib rg޻W"p62[c" epnn&ZsG`}Td=pVNIh2؀r .2R9*aC*o>JPe- ~tF.-Ff4'W;o \"'f&-NŤCA"Z5><e}QչRe xˇN-d}_(.sMb1Z,=;&HikIW`(|sN. c`jOW ^wv} cBZsgݒi/j#ݟXL%(:Ԧo'N3} D䩗 6TBW$4.OgݛE~"òk{Idʷn}YRn1n,--e"Rܹvueh@wÔn4™bvcXYs܃柢Kn":,2Ҕ՝i Uvډ0s* t eN}㊈mVwBO99+7u[@ ě(>V-UY%O%m _0k[%Wh.b%!' y14'&wBx)y>$sHln-DeҝΨ68W^wCc/9m]N+O9)%/R.gZ "B294lא:IVKTBP/#X4})* QYǼjpqV:q8eH+_vۍhմ{b.՗ WQݠ·T%|oa)@8*kҭFcGD$5=Z:6$p9=Қ9%Q-?.6hU_np}QKL*`?jڱ6t{:<. Og#9YׁHE^7TH6CEzxK!Dj}&2ˋKk_U>m16NqGʨ4';] -{3I}LC)u 3IHD(hr+G\ᠵe*Ucߊ!^s}z8e*U6YrkK>Dq#įzs1P/ L*rr7->R?*T&e,&{4n1gyC y[T3PPENbc#&1,pEUɄ%I%yrGcօ8''jkhNevMRԆ]J:'csT)RSN(kåqǡ]嘮6iѴU:AD[^Fv\ksqHnGIee))IVt_`ܤȃt*ؠ&yݔ)Us=ʎ3}i}ޞ1"j䥅am~8fݣ-;Ԥ\-r, ^;/sH1>$ *֪,ˁ!)iNpW Wx5[\5tFf&sE{U*{=MpD5ֶ5\"{((u CC) RVc>y5+Hd6IkknAN7qLwQ%-JBU67e` s|uŹXDjVpPR #⩍un\H_ ɄbHͭlRw'sW-Rj~IkS2Zu0RA ( 6[gwZHёڔQRAE)l9+J^iD©LB~ty:܋{*~[[}VHRyh6=Q^qi6˯K0n8p1ӧoYtG=!Pu(^ W2M=xޏn 3-R. *Fҁ*g RhevV@q% O;UɈoXaqy& X&RIV!I*!$ mtsJ_DP?bN\'eN4hR7G I Qfn[w(52งRֽ j@NE\\mQvP$yd浻̀Ǽ\n.mڒH$inwdw)53vviy|"8pV'ځP$rKm7 7=Jx>ؔN)+Nܠ}ySPXnR÷^dxf<8ppw$5\[E=70WCe۞|Y/3dpp7'hӉ }36ҩқz]5ʓ%a'Npq.Mz(bcn-jڔ+O Szx\c[&B\oT2*yU~:QuQtDFɏR1eKJJJԜ gE3Pޓ!E/whcH!Z[jhTpFI-Xԑ)uE{5ʲ&lDvm|х$q)|ѷ8UQEEPQ@QEQEEPQ@QEQEEPQ@QEU?p_ks8ZuB\ JGf訖-ݮi#jP>Uf7 ݤ` ~E0~M $<5bAV:5Wo#OCiiH}N'.~7Z|PEUihQEW?LnzAd|<2g]\[AhpHek#_oTq5J\3ꖨ+MxC?~ƒcaZr)% 2GH#}'rvKGW >{*4=w)'#~&4EQhG!am-#(});vO)m= a9)Pq%?*ݰ\Rl!HJr8P=m/UaBZuAg9?VG}c^WRήlaIlʎ|\T}]bg)Kg'Gc]jy%5m $Ǝ/Rk>W9 qn,`~uQu%c1v8?}% D[C@ f)?)\#6oR^\%'k5Z*&+.8zl8W#r::hM%%YuyKyES Z;I7gz͟mڔSJͥ `1ǘ<ý}(MRߴ+Nݮk#|d W`?aTnu .P"1Qڐ !?? qYñTijW:$)'G1$AڑA4ݷQj,-KiN*njFHq 烞@%}O7M\:6dg%a**ei2GzQ Aڬ~-Q,v?TZٍqitV<Ϫy^׽&Cx?[]-uTNFdž:upE>< 9ۃ[!}]P+5Gr 8~&qҡ6kulgJ۽ ˊ%EҰ+sS.5[*)8H8yۤd$Rҫ.0 4R AVTOeVqX؊r49I4ʳo·ŒZFKn] [-,ۆRc3旹uv#C)$GkAWp`uUEt|&X(DK8nH8N3K#e[%vV"],BUnrR C-(#(m*JrIzja tӼ)*ry?O+EEl=ҕMlېgbRjK%@BV Gڒqg@V`Mv]S-6e R<^G>jMpiǽIC [+!.+?H>Uu_cq^v;ejTu<ތjQi5ؽ>;[ծZ.-8AZ뵴Dc𮕪qm_-%8Umĩ_ՃYhʎ ϋ))dеӺrg{Ax*,$HKKmV㎨a$)ݱ7 ]im{JޡJY!ӂF p)Wi>qvi&ۘYr+Ҙt R$ϝFO|Զkqa: Q""pEB7TΓhZ>&u-=j}uD^`r@z{DwGQֺWq&B)3K]MPgz3X=?]uTZ̧跰 ]8'R4e\JpUʰq'O SM|[mgDسgҲFv!{m*'VF˴ލ[D   NGco#یw[.D{]Sؿ@ ){B @P,˯i:v &$79 r͹l& >ˎ`N}If[ۚ-+VJZeN)Br OY8zLE\"÷3wMԋىyXB<)'ƼmoHH Hp^؊ԹRKe8O gʀsڿO刖&[aȮd+H#$x`Z2ŪpSM%֐VPRvB89-->']XJ.:H6rH<<ը4M}u{h%n> G.6PA%'w,gMCr@jyõ hZB!Էf+6Pք;c*/F*֫$نm[!AKۨPmE_()'8! E}9tGEIűBy)'w+p%Ğ7g@N֚ng2GY]S-:mTN'}Ycw)pv+<[nq3&p!p2 Ji6™_wTt#ʛQy@DK=,D-V%32@P~86}7D iP0'Zq7;LS礣7d876ʊ`m33uKhOi[\Rթ*zRI!)^y qqZFf2 lU* p$ҵYw`xMϼE*\pڜb; t,Ұ2HIZKq[ԕXm\Yf\$mJF<^i>SWd" ;op5a[1Zʌg$ R^lD:u&31!nHِSA!pA mqEhQF143C@㷗ʎJ>f D M.xYjD*)-8t%)A xDޥq)svFeLQI";(FV!.  ~֍'YM3Nv n:jJi EZĝ(ΩTC6O\ 7wd4* "b+ྗĞp4Y(%E 8᝚uk2[N--%lP)!ieB}j83oL^T DPB\e5ԕ, xN LN<͒xVn.K At')'Rպ] %~ChJ\ Z}mn8΢-hIl?)m˒ő%ƐZ[QKS i2*MٍᦴdkELDO8ۺg!F8:KEPQ@Vݸ+Vݸ+Vy;'梬_*?xj*huu*+FtJ<_皜O\nzڊ((3V?$WUʹ{xkGUT7YŶN̼6j\? ;c|@MS2 +a#j/̝뫆FqZv #PP`Zz[HJC uT:2^ӳْR^eujjaP]Ûh,j]v$] qB dQmy52w&A+K IRQeGn2'sSy qՆRlϴ[LVK:Ds&Fz~r{ps12Im@Gꭶt5 ܋~۱@y;H jͅdEEDܭ׎%܄2M^Nq5F`+muqtzTB7d+nN3=lJa"CЎ Y99K_'iYr#g6?e_YY bIl(RU@{yUM(7:n-Uջ5x(OTFRQNwdIrM.qd }|T-A[n%)pm9A)*e>^U[[X=lyovL|)e X'Q]2q@Vq$seI-H)+"gxYͬacO _Uhd/iKN!/\^JI! r?dJ6'22v8Sf>ZYLxIa\y c뫚JJ[h[ZX mߚDWBR29sEkm_AWcPVvPwnqʴ-KfW'<{[1Rн7tjVQELHwk\ iqRAN}hR/n&鬪B֒%KCd^Q2Nis? W`.}!RՇ #)#\ ZyvI'.-OкJaCWQ'/<9uc\BžpFN!K>,-)Kj'ó (}if-suWOO*JA*;BJ1kV| +nQZf! 9iC(DoҮ2O4ZɗvvViڜCuN} g" gKjX#9m͏8%J` 瑜s;~xODu4mEKFR:仕+KA@}M4oA?B-%;Қq#9@8eޙiQ$^_.nN:BQ%!%d9ܐA׶w6#eNrl) [ZBIN9Of$ZZaS >+ƈPP F )KJFHߧ-Yu.ڰ.3sڗbh qJ^(Ru*TYvYJ2ac - NX9fݭɟ-÷^L! Ym;-yR pTp)Bn}nI&dUˎw๱;Ь@q($}Hܠݘ0.(?qSkK.!ÃBHQRY* 3@qiDdrJ,_lS*qV%EgfW U5)W2{ ޓ *moIiwl7 8;!y`r3 [lpme ~Ԅ8;g=1Q̹ rhh6-ogrh'hsq{rە=lC -kuą-MV◽E'Cڑv9Yj2[W* QPm@%)'>x4ݩ/w~MKlkJmH2%7,%v WSZu @%ɴCCPBF2B\ص,+!c)yёO-( 6TRChO>jiUFχn,4- {A?P m:m'ykM[hUq.6rv;݄HJKiߪc Hu ( C)Tfw(QEz:G <**{Ǐ ۾.u l0?N2.ҵKuaiCǖ`{ ZmN0P-/ BpW߿iݣ'ӓQN]F,kNtZX?f33Q*ʊT#SvA[Hٱ~ #t-nUOIYІE;spkOD䯢ӛwt9V>?Գ̟u~s-jޕ$sN zJ*)||<1\Ֆܲ< گW5hOoiGT<E%-%oon;je2 IJ1Dzƻ>-Vi9$g^HC:{ J.RR_+lRaժeҤ,y7E*}v1+O XwΣW ͤge$<ґy8S^~Eix< R*݇ʸU[ε1xMwum1dĺ$F#9qyǼ !ޑ99)\Z+A]7nQAuOǰn0*kRx~:hb#yreYB퀒,Ѝ"ErPJTShш\][tx!tw TAs ZRӽ9`{s[hl2Ũ^ɐ{ݒ斉=AH4S1AnKJJR2lXH֘sِTGRI\/Hto,jahwjԮZk"|(W)}?$jK&DwH(#j#SO\IUؾmjEܶhOā]J25an6]V]Nr3J?]zݣJu0_prK䟣]MH`&l Oڥ5ngiBNddg3ێ:kXY835Ȓ6S*ԋZrPrCChJV p ^1nnVf\pZpJAAMW&8:31QS!+D4V̔)*IPV'f? Wxܶom%.l8C%XM?,O~p7&#[&7M,q JSmIۇxof^G+}=-njLqVv,LHK8H8#h}g7R ϫ)uD(k{}<R|C yVނKޚ4[t$y6N܇hP>\yP{<5l6l\bqLK%.' AH 4'\7hr6#;!մTOXY'!9]`=kmzyaJvj@ J[B'$U6=R* VREg-'r>T{VL{VgS5zm)1}' oJI)x84/Y}#"ěA,G $e'*Ј0 myMuO6o/)_W'/nhRLrC|^ϟ€jN։PmHWTGw)%%$9'&߯Vgt,`ݸowSk_!^{<:#ؗYamu%R,h *lS Pɐ1c?zHPZTR{},ib֝ѕ5,t 1 m;)6HܝI$=_쵝Q1\Za î!˓k,% )Y)J [delipWqܜCjƒRWγ7խn M=ԥԤ^>܂|@tS0S۝]ޠ RR!JJd|iKzm7u7LÁ$Hu nԂ9ȤKJ0iaP&uYKl h4Rrr3 (Ӻr56$GzcʐXD!)@ H}&\;O=wY9y2ۘ$[ q m)9P;y8Kifs |KaiaGZwm3]TC %HJʒp%&-S\.ΛZ)T y#ĮJXNӻ/In{"ŝ)0ˌ-t: hAڢQГ|=o:ItFLœ.;wb\pRO{KH!eGHCj05Ueςѓ쉋m)(rpp];tyά]n;!K")為!( !EkV²H8-|hhm0%K $ ՌȁRpN8ܫ~a3*~ŭ7Qm RRpTx-yryݠ@Pm>FIje@7WII7@|ۤ۔;=1[1kdա`K@)%%y#=-.ǚ~`D{ӘZ|/VKW#KԩP#u"t V;[)*Pugi`Eo!) R09)$ AVjM>Li=+u(0;5ƔܘWkG6mӍe\$JF \5iƒkPm!M(--%YS$*b7kv & |)] I HHŠ@}YX'ٓq 6յZPԜrrKnˑbڬqKL{HML%%+!,BWjO9\udݏ_}v-n!8a$4BN7 inMNJS¤!,-泂wT㧭4t(MxI BOfww uyQBCeI+z('$‌Z|ȋ`#mqT[ye?my-g~P(Bzk.]lp${.9MqxFX9Y%7 Cn:1J--wp8BhxPw{s^;-F"tmZq!ƞBTe[kܢ+9ZDžޟ"m\FyC Iq-ڑ逢eA /UP3nY]PqvMNNp2{۽ȩ,5SvJ_Cy)H (se 9cz=owQL0uTIApmNa*#m]Η.4Q퓯LYq`lQ` "Jik;UhLɑݒt&t6Jq)Q+%c $r>1&@?yh%\y%^P׀0p*jsutZ.QZqJZ+SN6'9),Si#WX-Tµ-000RdAuGkmZ4&m7w7f\i+p8JN2kƅ]i3]zƠ•$6$i8ljloH2 yR6Ll0'*$ܔ%\rr ;D5yUȕ x[_v_4gNޏҳ3R62%]LH$'khpRVՀ=WMahTHvHȚQfsTn J<%xprbiz&l[0CAqKe*d]5CȢ$ZSC6ykU]!E卬]5ɣRk4{)׏ qͧ|4.gc vݸ67$eY `yb/952|)T`>UW_K-eT#erzBmAFo|NCV5GJꀂI9Z7#.io sS!S}URS;T*)j|q!z 85$ZPh"숹ڴ܅ Vd:j+u-E)iHJ7 qK>ʱ5+N= '-,L_"}MjϬTϿΣk̻]^-d8r\ZgS9s6E2:SuvNn#9VϲpƔi#'*${tjUiR(~b' <wm%3v=*RzB۞^矱fю{?WCWVw.l[J>HXշ--yYy4rOJB {AOj{^ڭ$~ hmyoIRՒw21ˆ@ꜻ+T'Z>S榛9x#'+Z@j"1p^&SBv@ϐš MZrB{Vuq!tlG'FկfD[DE\~lkDteEUl` ŽOuU 鸟zbJ[S}DbIB9 ^rCpi\y2_ZBVr@$Gic.xnF4Vm{V`-[η+N"\%Ty8o)MŒΟU[SE/;wrIQ9$uo ?ۚuM uR¸Qp |IV0usNٯ a*j&0$%e*m]0Nlv[S = gşq7=r#1 L~뱋CRnlpuS#4{Anr-NmOξ.)qԄ(* 㓓IvmW60H3ٯ:}Ix8ʀaO[]MnUTjgYVN| '%=]jVnl5h5pOJ;Á(R{ P827ٲe-InCy&KO];xFvo\ȳT-P;ÍlShPS%;BO#%3* jXCQ cH2z1v}LCEA[)HxH Gk<|ySdWmm.16{{}:8Z[p*ܠQ'$S6+uT~kr#:jZ j8)QHi[ JEmxI-*y六X8'TciIo5hZi_#J7wBTtר5"d%(+Qw9OqSXwHS9I{1r~ii2 CEy6::wiSH ΞS4$UJeP*>2IR]1%}p)%'H ;qkv6xl)~Ƽ:vprn(WI 'ض;C(PcECԕ-q8Z[UXݰ:1[3m.AR?VmmHp~iyxIRS) ^*9yUfۊL'B$TG>i ▢ǙCdv|otTƚ!)XGƧZl'n >D}"iJݏg1uJMX͒d 4sQRHڒI10]߹y:!(Ǟ+UTHPjq)m!n$Ϟ3OI)eNRN@W|+sa}}CBbՑL]= !/@*-ޔwc B~ > 8 Jkd[XbN5 E@XpJ}=B>䊥QB5 ?H}g9 ?p!`܀z~sȦ}y8UI[Q<'Hjg;Liz<9֩],ʜqƜ@qdAȊLݯ'5eEiqsg6jP@S 6~Z~mron[&r7inYt%GA#^16*-Y3 @g*C. Sy!ݐjemgֺ+ܮUnjVcU-<[պ:*3BwW&<(̾œCIJ7 2R<&lw+[n2!iC2sDe<\5o*)VcUM J^BCیq -\Kϗ亻c:Ŏ[aq7㢜*I Zž榗oĚK'Ymp gOGEչPwm΍p'ZT*@U"-_LCy[ e9-xZ+fb$ (p# ԒH9yrsz&ob2컹i%׽b*A!n^"6ĠSn|]$oN$1[ m`(An 46>oBۺ)uFթ#%! 'jq( BmxY2#W Ҙ euC;\*u8N;N,?s4=ZT[ာ-A(^g501.^-]rj"/>yXYlw}[nddC^ҳ#^f2 %)[$rH<}zm_truL(p"dR]*x۵\yP N.w(M3ep]7ޜm#B:R7 ' MCkӞ*ʒC`JR}(N3a:K[Z^e=Imrܜy!-/ ڬMkƚf ٪ċ-ui ٷ*WFGnڑNnLFҞ} S^;@RT=njl8D"&ֆ,RL(HxKm-I uJ<@d,ߵUq3yM1PԵ:%II)$ľsVK"k0{VNc?K`]hRrIߴڞO!8Ӌul$Kj$0X. -\YO"{o(|(p`' `M]_}2|qv:Ĉ-6\t!E[;8v )k=z7G.MH~+In4RRJ1!eIQ>ժސm8D-O)0;}l)\ E&:E*+GOڙPHSI‚pv( OV/[^+-6MF08@se2$y}]hVwnR3& uušhd)IVѐSlZ#N(S\"6G[20(S%EE1ĝY9+Liω=ZzҩДE|lS R*ڊ(+~?zVjǫVWkL"RFq[;c;H[Jnдо?b{͑o?xj*6Xi.@2@HiCQ_>Hv4>GW昮FtJ<~BuQEVEP7MITktՍ; Q#>@ qQRVjQa侚֟3H!ڊר^z59 v1tWk }%4ELiB.I{ҺaC:r QO8>+J>d+NY &ʰ|S{ R]JtƚJOZBpKiNY OVE.3?.wwkӈsWt4gTEKM!UƯzwsoP5mV R|aEFj']SFViami*<-?ILk/K$7҆})*U('urjY񴦗Qi; /n iIǼSKɻ7ſ٧*eNwjF[C8Қ\yi85gN?MwR-}^J9g7+ݟ꧊)^_WFz;I~gf/{_OTWb>c%,_f?JX~>MM>3+%?lۡ:+ )œhQQVH:M;rEgV''[ڢI2OydƴקQ:\K3nKie-/)ģpRU&=/V{7ruWTi[ H$\RфGyfO^ Inz%r4t[[RJABPn1W|F+QICHJU@Pe)˃iK.|xHL CF:.+ 5a5vnsרMJ3Qa|4zV6mBrՉLTlRه9Kju~[)A9 n @Rnv \m~Uq$(Lz`[G | uԙ3ZS\>T;P$8mZ5U-I]+!X[M- IRd88z) ״ۮW &>[‹η)U)E@GPqM䄩8|;[oYg]LMVuBJ›e{Qp>dJ|e8jt z*IK&8\9HA! xQu[u579q&Z%HmیkKu!/2P!x#hj&ʺ7lb~nH-V2S194@SֻZI]̺mDrl*tBZ%A9*ɢlrK WjiGX9CD9Amh84ʖ^VU+ݶnvڇS\Ȼ }W'2n#%R6F̨;F1dlթ.9^ufz4+JжARrO T7XOɾ[\ T KDE%BFZ7R5`ەbvuLb S3m,L%⴨(a7-4n7m:ZJT- SZN/TˁM-q>(q€<0ͩ&צȶ2"k,Q8)$ s\׈R$GϵJVTBJQ^KVtNO%LUaQJ鸤)͌e#<4ɷyb6|ԭ¥!dՁ^uqUVBj ^Hڭ.[Uki+aPFp#TudJ]\y݊Vԏn ?SnwR\; P8YRSOd|d*x]VY2ZvMy-!NH};ڏ$mRɗ˭J-HΦL-7hP- %ip;'*<E]V}'dYXwALHՕ-ŐN >Ii] rD)[8ˊi@ dds@SuvZ-9ʴq[nue [mSw+rH?"| $!bzAb.J،8'$֊(/j?ꘙdֲ幩S=Pq"$v8RX%=W<ڌ$2SsmnlmZtj|eBTHK%^\`dwve8S\(m22 qS[_*eu iK8-'!I8 iCQ_>Hv4>GW.%'i.2[Iʆ}$N?cUKޑ?&ڣH_UvIpexw`QUKޑ?&ڣH_S$1XUOɯiy'T. gyx_k>Z^~52K^%EW-/?zDj"~MLxw`QUKޑ?&ڣH_S$1XUOɯiy'T. gyx_k>Z^~52K^%EW-/?zDj"~MLxw`QUKޑ?&ڣH_S$1XUOɯiy'T. gyx_k>Z^~52K^%EW-/?zDj"~MLxw`QUKޑ?&ڣH_S$1XUOɯiy'T. gyx_k>Z^~52K^#oih/53hs+a+CJi QP L%IIf΍ŚLcH*u[)Q) \vlC>4gbRӍ9-6rFFkk˜ ?ŴZqNC{HLWD*mnR#>I[lmL9M)[K)Z0A{+O+ۛJ)%^p$a Q g'f%d;aZZiza ar!Oɍl!u F q.%A v-plS&!rguq ڸq I’8)k>Z^~52Kw\F^X>-rZ>ILV[퍔7x6]޷"{VAR!{ʖNBm.$d4iy'T|kd5/fnB̲,˟%"<0PP2*ڏI1cOq[kEo68 ,8[w8 (Ȭ+X]T\TEhD&< ;EBUuSai#\%”ݢL.IŸĐeĖ YIS_"~MGKޑ?&ڦIpcY"4B- oUi}0ܒNHܖJ8O>g4B\cRh9._aJuvC*3z) x)G°T)|k>Z^~52KwSد>סJtXoOڧCVd4Cj/49 5Y%o'Tخ6#ަ+Sp\;YJOUpJAA"aOɯiy'T. k!^$2duQEp{ - A-++1M5&KiL=(,G$u$!$ C!J x5.ԓ~P۝ieȌRoNJjrpp28mFi,l%$a)w2K'Y+/s~;ZY7"tE1ЕKDUH9$p-cE`'n[jcN;5'%2(#4Oɯiy'T. /'vQtb-Ylun\-&4,cmO6`M qhRU{q m&8OQ);vKޑ?&ڤPo\n؍ɸJ^=E! 8+RIpcY"oę6[iݷ˾\6&kie-<,0CM#['9f#XiȱUoyV﹙s,jekDmR@RBiJ.8tSRQ)`+9`/Ihd>Y]ysF&>1IC9ƽy4y' y{}&>_H?8עC9Ɯ?d}/u⏤-<H?8Ӗ윂_>\Q=!@NZ·r >痺L}qGc늙uk.Ɯ?d}/u ⏤9߽::?d}/u⏤>{uz{YNA'KI(L}qL!_νq:rt\SzA_Μ?d}/t⏤s|Ϯ9k:~%^c1I):ס>ty' y{_}&>uq]_:rt<<җc>\Sxq]_:8~ty'!y{}&> }cB9k:~d^31I)-_X[ϙ-gC9 s$&>1;3^3Yy'!y{}&>kir}ZPj- !֋v"vK{SWrg_HJ #+?d2/u⏤oպ^g-ot ŴY@r@"lIqʢ4RYl ^O\8>UZ\S}i =$ _K:$q[-78-ζ͏6+ߎ\mx88RIGNX·r>痺LL}qGcb4;,%=dCvsi}D *T>yp~TS9 s$}&>1<3^3NZ·r>痺HL}qGcu+W5Zrt<<_c>\SZJΜ?d2}/t⏤\_?:ĸ9k:~d^G1I)b\_WΜ?d2}/t⏤]s^\q_:rt\S9u 89k:~%^g1I)ׅ_Μ?d}/t⏤;u|Ӗ윂_\Q_W_S윂O~\QK:ί :Χ-gC9sA&>1M:5~NZ·r >痺L}qGc늖\ uek8HݏHx#)n_ ~e9k:~$^1I*Qo8 %*\:2$-TpiYNA'Q_I(L}qS%T?r-K}• XӖ윂O>\Q sxd%}cNZ·r >痺L}qGc늓)2vC2|;ݓ<$Ka; pO GiYNA'Z#\Bfu[ƛyySiWm]leɚJcqvkě[k-4|O5ΫЭ &'_ʁ 2   `g'e]< dBU@(Bw[5튵 k4fYYFk4E5k 4E5k 4E555h`5554E dD[[Ъ" z j wQkЪպ -UUi BxUzZ "^V}o .YX]UUsB]U^YWN]kAp2b:$dZgN! RYCBAI88r{[5iF[]];Pst (lVG;yDW:(U&TYk3r}ԑ޻h;|8kyjsOL=cf6[ISd:3yE}\''Tt{T3LhJZp䔼9[ꔞN,CMHeL\mc JA +dK^ВiO(1'Eէ*c/w-dc) 8\C-B{fw$dM$% % `Z'ċ:2Ke4PȢ$-٧曤+^p F0 "]/:!O-nBH5L}/w.:<ꖔp'zQkv%oyDUָ(զ; pqI)1II ' A+ն}5o/B*Ҵw߿q #T@͡"4uAZ?P"8%mua YHWs}Ӛ3ϱZlP-f[m.UЫͥN+ZLn!ZJIPvy@]%7BS, c"[[ARd%_خ$GLqiIe pYRnC@2)+%C)X;J"zgU&z9cCu (RWzxfM0*Zo)+nBppFF?UyL|ݧrӻ gAӁu]q+(• #&w2]d$NZ2E7)Zp)ԑ?)Û6ܙ*c)+ OZ门,PrVT2%eJѴ`xUkazN1nS!KBʖv )}Ne1.l<(q^ >:戭׹yw "^jܧcݕn CE, ,' q*Z (qנ,$Ԃ5]=&m %!KKP!d`+9PJEjmldJuD[*mG2i Jw-8ZJHVx5ѣM)f*oK.![&YYN∝jM? 2k-wЎ9Vj6$\*mLc))H#R-w-9kbKq>`āĕٕٯcjd~ ѝ[CISB۩B0crI98"BЪѺDQ=D [>3Cv J VkTk j%YaLl@ZBYycҮkVKnGv#pIʻp~5ë ) u1DWV-J EdǾY4cݠ:TZIœd n|W!I`6T]k'z:q݃yq*=3F[, >ZB#qʉ4łj]\㥖X-*DP:p~8(\:`ޥǁN AQ 2EoP}—uZ\\opNҒ2HQmQC1y ^R"ڐvMUgyfu-6;XRemnHĄ-G9@9\hw#K2Irɷf"UiԩAS$z7QF7>7QF7uy7ѾQ7֍n-UUhF"U^Vn-ªպD[WN iUc aUxUZW-k ˆ&c#Έ&e5E,k$:ue^fy"5y3D^^fW4Er5%kFAIZVp%Gjt5UNoR,%z9OL$+֭KC_3v~Z+_mKΏgSдRe޻5\!-mI Lq5kn p-z*$gb4h$)hIUmVk_XOa.ZuQDVݭa?Y /1I4Qk_XOa.l 8vaPV;O$'jj=*S`i\F i#XH[0 6%p;€(I6ROؤ .W ]NF@8=3[r:s+_xr1UW,KyQVN#~T-d6b/ZT̞З%qS!U *ꤑ}S*c#]ZT9Skb~%GTgmj- 0-z*m4y IRI'i[R\Kj"!,CIIBsi`< $sS"1;$ڴIglSK?$)&Tib$2W[ɗA;O_W>j+}x1$﮶gybx6(2:kvsRnj0W' RJHY΍4 :2E+bNO{=)6'qiG@\%oWӪ֍l9:][ Tcx<&AbcVvK=G5-72Q},'[30ih"%U"1TTL]H̷i)r/3"C)]v3J[QRk$I$׷~ZLƀƝPP,ؔ$zJR `t_du)R_x tKF ?CQO&مVl~ԣo,r3sҮI2zlUCeMV:*Z}fbmu?up/G3&;ͩYvpRB T X Aj+Fv#NpGreP~;T.8="e֩ds-d)DrqF=eONǀ\7}6̧o}tN0=]3 X[a̙lRjx^vI@Z)ϩ^KNۥj Ҥ}qԶHVu5u+c1g /$В慐[ aHdprG5-{zVv9L1<"M!]7xTMˋgZfyT·]WbN8{er !b3Q+D=L~XPjC5l~7}{hSd9MGRS%1$dVE-}[u:dJ=3[9=RETQ,YeǑҤ4qǂH#ZMgve}D Z)[w/qy%YJmFBx{NϷo?xZ/KKS:wJ4;ǥ-[:fuE~AxnWCUJ?aqV׮ 3seQ4a9;.Zb7In[*;%JP8H^1g0wҙyJO>Y<*Һ+M[%OX^ai lW#pzջs ^Gp˽E#s/UE GÑ$sK2w4d!ߚ68i%Y)=nviya}ޝfZ#)KMZٴӐ[G+9Q$5X5Z0[ ,Fߪ6K{\T;YNuFq{񋤇q(Q;{>Y8漅V!pU ܭ\l_i&mM>%IWBAZޟݪoP; &3E>RbEͻ~^4†/W5.y%8KH7T.銏;@BC+`X?:^ǡU/ p9d۵]Z%6S$c ;ҚNչ2[SJZKLxe@sGGj3[׏rp?J?Fj˳7K6wm6T(aO5^Y;zr7( G%GÎƓi٬dz\OD*$Z;'je렷ڒ$ գ01ֳ$cO42f\c־ԗMƻgK?}Y )*GAZ,02,M.L$'gWjTw [{zaDۦu5UXħad9 IW'wRh۾nwEIe. *}#t}V˪J>jyn}Vˣֿ]TtQk_XOa.ZuQDVݭa?>k ?GE}κ L`ڌFBU# STv)зcSsɚuKIlˋ!yB@DVo F~w!}(q ta. is>`_*n;D-WnfSs$W7nfSs$V*+4zEUZEQDEQDAR]ggT_NϫF!'{8 J?+Sm݅tohW+y†҄8$KP tE.k% l%Dq)Eb))e,8P9Z)Βpy*ao pRݝrfDou[V~|EPvE#ғa ꂥ*U2BdHa1qU/Nu[^W,E^מG£P͵/PF2PZT46i[i[@Iǐ5Zm{]yrCHTSW&l 8F>5nvʟ*S{$ǖqvr2*94eݷv'U$ q[6Tۈ>c9IǖYҧ$SyXB!|c[5&i且@9CӶfѵblJBi+B:W*xǶRӒĦ1SmBy#EI3x0s~)^ {$' |)ZϏ VsW1mXgJJ(ӥ;;|zERHBqU2NnqX*e@imPNU>Dn8=fZI\B>#גj2>4wݺ+FV_5藁U_;me[C#BzSݛT!KhnhmKJq1˫V=jΪ6)TRT 䀟MGos"[51\JZ‰L3ee qt;\qֻ :vc`KZ575f+s 7wey\bv.)f2K! AxkeէhL|P 85+LA[b8S"2R/=kLi{E"/e!E^8Alf=Lm| %Nlb-"0V^T(,Z$:NQQsSۯ\#oi%F:pt}RވDӍ>DoRTPKx `{ߠj X礷con۟: m0\um,XlpDžѮ.s4MYQ(ueԥx6l"r_1,L'vݯ~[iYZ.Ÿ0i⴨+zC'6[9m+$ЭS VT չ6M)${.wze%idYr6 C.Xy:H }ULJ/I-e%y'rpO5 uֹ2}!~%>qP^p >qRris2r !<CVmڻ?IhDr T5roU)ȋ-> c^׷{d6hJi1R\FᔂzyxRlwO/ d`)k䛼ԶJ[GttP تj/ {6P~P)5LWPTUfNWc?~Ip$2[PV>hyAfJ.܎N݇ciA8RsǸ\3T:ƆeJtnCJ F|*ߓ5 Vm{j-Sh .\uژҲ=+tJJQ=qҔu#)[i%Y+Y@eAٔ])l)Xp3K Ǟ > ,\PHBE|SVi\Ƚ>ﬤꝤqN1TIMwO\##Q*ۻ<=1Ƨ];o&қaH'8d[)i&K:򴩞-%3)KZA:ܨAQVt)PV ai;RHjܶcm9W gh=@㧺cu͘Lw(܌(\t#UU9)֝rQ N:ַ9l}υE>ֻHrK%;mߵ>'8VhvQ `;VڅvGbl-g40>Oʗ 4̭%C&1tjSRPV ±=m @Uٮ ī/aYAiv/A1Ӹ?\V|מX|1xp8`:!eMR5eMR5QEQEQEQؕPOu q-0ר%d)汸iΫ>?zn)яfs]xOp!s@g4k$?c4Sm7 nt{C_3v~Z+雷C_3v~Z+\v^t{=BX*["("( !}1/pFR%'rJH#]y>Z]-i{^ s<z^5*uo}(ؖ!O'>_>ifJrXC~"D[lul5ġ`pSpT&JD it6f;+6-Q-e1a [8m8*puJ#wgbQ$|Qq!˚SL F_p*NJI>玿*`ݰrL˨mü8 ܁ޝ`|* gN/SWc]jkYTv #% -Ɩ,VX;=T߮s +FV_5WT_L`y_eCI>C&5JwVmY*Rx$D%czN1gaЩ8l#n=l+ʥx:\mO.Q$ r] XpQVqH]| 8)PE,^V*MF7IBlR$Yei Iw5lnL>42;)ݾlUKqq.8쥒$#[G㲝u%,I*8g}|)R vl{WF6F4Hnm#ԭ!;7aGJQFWW<;jį2R}\M'/$LS;}8xBFLxz&[ew-]ZXt+Q]J)M%@6*VpC ρ\^*egĢm:ݨ 8>UYrZn[a@? #O=i ?s׎.`ݫ*-kN8RUϟJ0#[]`:⢼hrh]3EfeC]ama;H n^ːῨ),)c:Ʌhp 6AO)2T0H皘&5l)HK@Vˆ#?d[$-E-uIARGxFMnu,y F_i&i3v|5D)I[(t!@X57ӧ9- !jG݊t̷LTI܎1 ## 1)Pk*Npy5OҭI}q_1 [.ZUCAiTXR 8+u 98Ϻ\ 9x͕~UMpݷ4mf[Tqnd*nͲdwJ\H-x)o^{Pue!x8m9c5lZ9Z&´*rXqKpNv=jB7_J~Ki([=ػOeD,ՏNĵ?qBl,.R q<:QѦ5L5yQc!%9w*8<+wg3)lER0/ *𩻿ik䄖U)< uǖjT02mS^[0+utbډ~!/onP۪N ˜X,b1Z)}\JB=u z4HuzQ-`Q9MlFm2&>*{N]sԎ+*L8Y}FSOK$mh~}sW$xe0'p!KEo hig5֘k%*m;$,a+VHZqʎ*.7:}-%+R*Ք|㚃k_^ _HٿGTGG¤+/(((;0%EF=K?kl&fvt54Sm7 ~t{C_3v~Z+雷C_3v~Z+\v^t{=BX*["("( ֱ;ayeAm7jd/i89|sX8M UfCwx.HLS2޶ZQ3'sVMa.SbywSC IJWʈVjG2vfBUTW7T;"v\T:' Nrޥ8w%%N3⥓i8564fڳЇ~8fAu"H9mmM@rmYsqht\hvP[Q]V#AVh: +nDAZ\ݧ9kݴ?mX6=W_o.7㔨ǼBOk*)}Am h׻%2փw>&{B욆wYrRTwVmHnCJj Bp Խ㩯UB?yCqqxItt}8+:}& n0l%͡sGO}\%=usQCMajINʐ~Xm:A^y*Nj-Ixґ.8R'bT?ݞkJ:Jڲӵ3}BMd)>ݚ~(1 q&ߦWlZU>EMBBge('EKN ׷o{+TX"זTCKl Z8dz'4RR(Rsrs|Rhn ܥ4\RJr xoDw͔z7`u hAqiSr|#KÃbw᮹Jrdzv#nZipm;9~|E#J=?Y7@ԆM,\U KT,RS2Ob*R gmN}%FwAkӣVa =NO5= ̴68q͖}wjU>pƾ>zAcNJBMW$8r6#Y*uwOV_d?Ʋ;q_fmR gWGcz٥^uԶ.orUӯ`(p>^m@aD?;$!&AAWi-FePPЄyR z̏Sr-): xk]e ^0NO=:PRN3kst%/K aw(G'R"TZ\daq2d{?BrpGM1!ɖöSS))Qak(>k\GZ 5%%|$(Oj-vvGx+Fʤ) ?Ni w&r\]U0˥*>[)Q|1Q:CZe0W{)BK mp9>YcȊXToos܎A}pp2:{sj'ĀՍVKc"HPIl$fYs 64X^:r=UL!J6y^?N\Edzȴtd˼ۦ" 8/Y^NN4k7K~6; sܵ(+p+ڸ"YwXMr݄ꌜB bkJձ8) ƠbkV+i"q֒QJ޽y5'ڞHĴD)֜ 'jz&ԡL87)H~x?fGLˆm+fcCv٤ tvI+B=[QH8BMN}nYllv ?cP袴q|sKCTwJ2xqJҒNTB3>PV,noZU٫VdܢɎu%HLB㧇D[)luCd'B7h&ZaҀ$53O|8 iz6 *ku9}9$U&Ida`7d:J[{snz ! G2JI)85E )Kdɀ#!?0Ofm*wDB_VY>[W?<xo 'PO©̺‘+~l0A\6o rQ+EQDEQDEQDO"S"~ao?[ 艈@Kڍ wQoܐ|@=${VA[t94S{BAAz=VQ쒔$wk[A§ݪdfݪdW7fP(VȢ((EEFYQDXTmUEnj cN6\R)9 `Gt=T16oĐdjR˩ BrTǬqN-.66ऎ]/ڒkߙ]:ݜI>4K'0 p̧!( QVp 8Cs+YN6S!}VssqYX\YEaecyQyVTQG{EDQEEH5YQDXTmUEnj cN6\R(d(A" jy-6bTmNOIn0o%29GU KM!i8)#A5s_tgw פLq_Y;"b:>VB/^M{f_yB8;`9ύKkM#a]S]BJlm j=6pkG>±UoC5mw+e)UVwmn/dDKR"R)G!;p(4NJ^rQ(yzd-(=ډZHFI=HKj lXw$)rr ; %y=Є9PZqsU{$FLdJ},! B\7+ ~mZE:aKpy*|rsEO[*lWl !ո0 #*<=J\K1k/ФIP8C:_$[NHp+ЙDf ns;, ^s)9'9Znjė1letm]Җ@RR@ޟAO5ȽzC>KZGj$x@01D^mTmUE^{EQEh򬨢,v*6*ʊ"fi5Cc)Z2 VNٵ<s1b*6D'fne} 7ݒVx*V֦B /BѺ_nk&8v0t 8/oږxڠA86 |*ICn)jnzۍUcvs$W2i>C ]}ae=u,F.5>mmR ‡> ,NR2+=ʽcyWG{EQEQEQEQE6hΦC¾y_}_믝4_kSQ_BWߝg-i_?;#ͯ|O6`U* UۡT?\-~ۡT?\-~n;} /:=,QEVEQEQEQEQEQEQEQEQEQEQEQEQEQE{ҎhsG4]sG4]sG4]sG4]sG4]sG4]+hK(yyEQEQEQEQEQEQEQEQEQEQEQEQEQEQf-jo1j*3kwND eEVUb}*xq[TWJqHX+(vG맛_)F:d1rݩɻ:R( i O7:=jt57jů"t57jů"UoYG%(ղ("("(W&TD t֗љE!FE&$:>nCb<9O:wRL\&'l2+ţ0(dzk ?`h7;9ՈNC֕!\TRʔ(L){"9Z^CYamTH2v.*InpFlb(ȥ_ԕ)C#0JFRXH8>U(j"{ @AwQK.0qY3ZM@d~ue^{2)"Ic^tqh0G?^g {J~ȣ"Gh0I|gĤ}YwN]u}. UJ+x|\dT1^ G PyN0>U.ExTwgʔ~:k;oϺ= ߦ>X)>Twgʔ>Zk;~7 Sov|ϕ(ԴjZk+~A6t oϕ!چ?%SZCWIyjąɘ~8$l1 fO4ldK z!'rv:|V4Ɗ# \g H `OG5A]=.Ȧ)V\ݎ쓌c9uD(eq cTM/2:ו c]ȲȮyOJZDW=ĨWXk1 {ZlJȣ"u (+wլe[.~OJߑXڳ",ap1ZTtˮ+h Nz?k?~qvkK A *":T[o?rX=nc9h xȅ?=IҜOo}í$v%ggv{  Uce8 <-Q;2Z2::v>gyb9pʖJXmLyQ͗\( mYu'εvd_j, q2 VA# ]:gU髍SY&j8f wyDvHC l+6<aFVSHG F8؞3wJ@PQtq/Iww'2pmdIryPV:TR f:^֚* qy嵅猧juuMуaZZJFw_R,kq'c˚vuJUi\,GSuF\7V-[җԟW#8oBu:#q+͍@$^JiFT5 e$fRݠEVON rXt{mx=<Ɣ "3KqKXlp>'f[ȵ\ujS/8>]PtlK&: G2V3 |TtMg8.O~U-.-lJIC8VF p%䎾uV}]bRA.7gƫ˔)vɎEˌImE*mC$U m\MֆF1 FV@Nu[V-uf=ݵ %GT?oup[^,-y5T$|ԴBG H]!H,٤L&iԯؘ%!`m*3}gg[iC*d$ל/Mj$IBU #WOLx־vhk'RCZ7qb#\;riDj>l2ڠf@ zɎDcEټxLIppR+1DFb6ƘܚfDaop;Vr2854FD#PR%qu,DwhOZcqORlҩ.ֆI*`qwHzUd1%bknC SrZC pESG 3Uj&LE}]!nܒiPvBxp}ơiJH'6S|[H[~{HǷ[m--<_.DPH$3Tq^ݥߟ%߹W-.89#'5Fq,e'rPS%6rRw.>@m]9` 6 ez|6HV,jƵE$G1e:qN,e@fg}7s%-JqFvm@XG@OS5K@݂/MI#`MdFZ]({Dr$XQXǐ1;)I>xZ.|9 +C8U;&u z qqfe&G k#vi=X*v˃uAksқHe-O0p[HXV: 9iNTi1y$gV<q^jP0gzHjcF雞klܵyeη.20Si#f82WS0 gek~P [^PJV0A 2I$Vi8= w:K뤧վ;x9iYIR3~jZ_^FlGbpۙB&qo$m8O fQo.DCG XSԌOPS8JA:6Sj/k$b0 ؞% _$K|H‚F2R#綴%tg}B]6jKj:PTRA~ڍX+#[DQN?U|kskIҌTHOʸJ~2}k;.)Tfše9W%L٦Jex@&*v4%Ɩr\Mi'#5 ܝdiRPR2g=b`7"dۄؑ!D0PVOrQ<+_oëpuQ@$tcjyMq {rz0:גgOjK~Z)xQZɧa4 ~Wh\ lTv݊Zķ63ǺVa.nАp \/[a| .}H+)\syqa*V}Jb.kW6D4OWRQW-!)-=(|A)-m%KNJA\ аZm(8@Tǣ%2wd<QMʻِmuި?Gun Ƹut}UqTWI=죔}T}bـT]Ω/mISW$w)u;m>b)k^.*/pfk[Xo ~*܀ˬIx~JS`(ueˆnAC!kGVk' f@mQLΫ/HGtGG=1khIs }hR^R SnhRhDS岼4spg\J[lM@nDt6>"ih׽^[BS9Lspi8_!Q_ ("("('~V=I}o vc٬LO7O6`R6Q4Sͯm7 ~t{C_3v~Z+雷C_3v~Z+\v^t{=BX*["("( .aM'HGPa]!$H8Vc13#v5\;pR RB S *P@g \u&Bq9ءȦ5~LPr\baSR\[zn[V뺦F<Ü}~;* /ĩ RJs@ջpGE\1൸^؅b7"Ea ~.wٞIxCZm$ۮ1(3dGfKn;A/CSjh4`֝iGԒe2@=͠PMy%f TKƹFGuͽ]ftǝJW=쿊#5f2%ԛݱ|n~*_'O?+ f;Z?K|CM%;bG~l<zKk=k龠mWU#ڡ r?bi-Y3G> ő!"2(RzqrbZɃIwJCHNj\6]HY? gV54mAwtv|&Ht@-/')ll <F..ᕏ|GGg4؟\7`~sۻr)Kޔ}Wd1iIKJHlnhWp' n콡i;ނmQ^Pxn$ d'駖-MQD 7ҧt$[$Wo.bhSˑK!$3/K#HhzLgiaj/rRKϞRs}$d`u⦬1.j,wQ#:V;#ǜ<[Rm V+2258e#g:N&*Fu5HS 7[JսIn8JRYTfwR1$Hm+\쮻7Nմ`nP2-oHidvduP=e)>x3SZDiMʍ-3YKz:6)p!Uu-VR m J?4vi,iZᾘ/%*c8&<}v7}g*2@85δ Ulhv[0S/gA҈j6Hyjr^A pNR^)~XFi+I!1\: .Y6PRQe%m'!IYWir9*"=}cp$.qcMN A@a<Ϗj2>Ud 9Qhf5c^}#gUdU;!1.c!ҕRwRg#ƯwXm$5-sv+[rRy szzgppҎpl}if{S +W-ue's^Xeh芥XڮBmbU7gݿ%=ɢUm-CBPF<^QZt\jsz=pKab4tyXI-;JڢN3?;AV} dx>d]"7pLDsUsXwq^;%ӅH8ܞ~Mڭv ɮJ>y<kd6^˛]wrCSJ 2EDik #-Ku,:P܃ Օ}%Rzk;>׿KrD[mmRF%\y5YwSXn^QԮs| eHܐH#r\ LIo<ӇxIFl 8#ڧLoHqA6]X}vT ,ב^]07J Ů"s%%+l&*JWt>]*s^YG¬PWœڤֺX\ɮ:סuWU7/?E*Sgk_ ܾ k3qݥw; Vwi)*x( ?WEfDg:ZjD۞ -#$}SaJ}c XBwq;?fp-{-iPJ;2Hxk{薶m]Qv䏶dưn+QEtSXUޒyVȉ2m?{XBp)#kn͝lDkūȩeUӆzF";lt͊a%GPR32klճrK6SH%*V0A ǼSgm8w(M\vnuW jR^&@CY=2<qMi[ W"ehKSDn29ԧi#6C IL6RŽSiZw:,+HI8 +FV2Rؑ}v.v-Uoj;٭ Lm K}acjҐnuWTEN(V{;t]vۍnB/ wi >#>Dv#^?SeJen_xy;8$:^R]moHN8RZԨP+Ğ+UcX_h˹6mIIެdtN*ӎq.0.c7q¼kj\DBYQ?J@K|]OSܫKaҜ>89l߃z^*g;3gS6&;ִ5>!k䶭V z8%R}lF) V- :Յ b$ZNFsWh M:vHX+?FYJwnX7UbI.S*)ϓn~J3> KҜ~anB%hB8 "kIQPqPOie!`bHgU:;:3{3Q>Uc\eۆ\wI-螆C+Jr:Z9/IMYÇj&&_EQDEQDEQDNэaN{4٧>ߵdzX+`[?E=HO/"~h9Tܵ]O_LݺOXЬQUjQEQEEOe+RnY G'3J w t(jig)}\Vp E ZK.jjKJ-֯gLwٍ-B@CeF99 <={laeN,HPTbSri dĊNJ,&ꬷ˺.nZduSvx=@➵޳]1>p}T'j[R?`gڍq]FOx9Qe_TBJwxY1 F֐2*oKmĺ,!:{h*0qmpJl6+)HLJ|[NBGzIG>"n녢'go*yj\٨P2V^'lm8t-=JOR:xOCSoCR ,4pU>*n d%N6F->@u:B(BԳ8ssUr0 å۫q8\;>JG1r8w8wb>ʗ57 -^I])6˔8 E9IS#>tɬŶn L>Ze--+%G.|xVx_QnT JG]} tg: yUF'(Z,u'_7<֧\<&֊q!_<`߭"ZOZcz /U}Rgr*׋([+$dNvvrآW|yH$-MlT;lV]جJ={NIBIڡw;sP-,BW@nR~'&oa5T|:Vm_, K`@hqEHlRNSx/m&..qv%W0܎[> mvF|wqҥPul%*\x8ۜ-.nz Kl̪h @S,# N}¶uYFHR~ypq"L"Bp]֪)cfW71PrfD/wD #E$vمm iᒹHRT9ǗN-Zfu*/6/nxf5OiM+J!iҢ'G"1(UuoTo V>uj#6j mu>ÁIM?Jϥu<' 'k JⴖNJZsx>\9# &k[-7Vr/Aڸ )Anq͙KUZom([ V#J [ťh$8֬T>TlSZe/vV3Pi6P3P u[,Nj,҄_8\VPR%PRD#p%+`t;Ao8?W%S8UCwWC2O|RI{%\$:zʋEm8^+rM§*F*FQEQEQEQf7E;Gf^j)ݏfVN??|Z?E>ZVspGjt57jů"t57jů"UoYG%(ղ("("J\ߵ֏9xc=);Ձ|`)٦ic He*n6NsN9-$%% )ǕU]^ 820$gEAiuas݅c8)-rIϞ\3f).4@–=qRH2-mMJ_xTr@zQYߢK[̭jqRڎF(NAce[ Ir4ڔH9d{o}?pHB9E$ ڮdyW屆l$c%@+Ny56àAl4T󍨅pG3ʷdZ]V*cp5IjiE(**$m?c=q~ڶ-j8 KDc@: rHFJXA|K@dґ52dow+B: ZMu!EpgQnZĹloJ^Y^Ԥc#$*n rl}F0FGT wx=WEI< {5F4Z C+ >] 9:#p48_Y ܂'9G֝Һ~4$,-Ű9YWH }$4KP(B֕=0wm9H4v2 $(ydE,SEcN%ڶ!!E;unC,Z)AN 1[JeioA]`ۤ7$hu=?]H ӽ0 Bѥ^BHinҼ'? N*=YBk'C3~EW- m\S.2փ9|)Whڥp= φюK߻AHCNnW4>t4- xVuwBC״e$?Q9Ny9Ͼ,#5/EfUMp<SֺX\],uCMw;rɂT_[FHhugeǩ^&B0P!@$S}lޤ4JK; Pg+YyDPy^YDتc4 iɁa.p۹z.u#k>͔kϯs[ڐr6; Rz6-^ԗD)叶#tjy}EsQ)zHԗ N۰=Dy-z{#xNwLIǻ)mGaCB *RyN2)mͲ›sr9(jk K JϢ7mWd%% $|r>j: "~~JJ+*[`08upUa*9 Ʊݪ̕D#b5&ꐫ_Wrd(rp: &S=2cwh=W2^2qW]PWҷB8" yZo_sa` Qܹ@b.ߣ#$k=?9#ȧj4D=9?mK̸U?Ꟈ- /tִZkMH\Tk7ҝR}CN-\D8=gy]Z!Thpj#0?IU5L0oٹZ~n!͡=Cxڑ[-U{~!AF38>'mRHcʼk[O8v-i>m!mdRKXd]4zH jNo!jG"%Jҏ2*C]XYb%s*ϙ )*Rw4v>uf\)]E6 cA@Eu\%8Ҏ0$}%2. ))m+Fy8x>u9&cΰU|α^@Hu=MEYGTGٿG¤*|QEQEQE:vkio/_ߵ=[9OO`RY?E>VspGjt57jů"t57jů"UoYG%(ղ("(" 'v9`} $)ҧh-ԶHQ)E;!OՒD6JZ+ $q]m׫JgmdqJKm;zp +% KZl)A#bO\S} \N]2)mWtR湺-w_%e DqBjW/5@4ZR(nBm!A>Oj~o&.=VTG;c{6MR۫j,]ŧH8Jʕ$ 4c*S'i)iYJʺG'Th7ߠt3BxBzuжȗ(3x9.(RwpyKJb;qT$wuNk#v<>\SZت{f\/)KsvNå2j[S!0ډ\į~#i[+NrZ>-I8EJH&+u̜W "*1G:+eZN Hm*{RSIIΡZn')Mj#~CyQX"=8Z֕HӎAWN£EXKeVZ2ZX%Lmhp(6Zzg;IH #WL@cҩ,{R {ܫqzxU~Weiz%WY{tz( G>8)#zFO#%Y9~fHƺ6c4w؈L:6J U}֚e%imRz']d,n`s?|㎵?J 3Tޗ.k'ufZCU<k'9>.5:ARRh O\Wuﴈq`Ɛ谀 רnԞ|}#Nv.#1ق(=]a![gX "-pfZ[+/.4;&ռ-)Bi-`A֞=r#o%n*)%X;KCeJs;.9:$Y\,]&QQwq]+dDž=wk,n7H/+yϮ° 3%Yjrl wpV!)) > *N4=Gt.5pXJ_OcA@%2$U !u*njXW`5dm,M4WH)BB@' *z$e ۀxHWo-694i=‘RFP#ߞ] W馨fDPHb+8љGGuVǵR4)n~ݩ'’] xaM$Ì8PJ$X ǾYCog?Ԫi(4UuC|a5owi5jIM"[tsK;)$YitY[A?d\QJV88f:yUj{<+Qiu*xK?Q:' ?hf)p1Ұx0S ,x9溇_rMPmA(Q5U²' g~i0OY^=սu[XCO)(VR~ъJ]Zw)NRARJ¸]ѲүQ?*dV.f ]:RP2Ѕb˜ZK8:+KHΨt8*J#*s/sΉG8A_O=oQ"$>ജ J'$𯚯[Mi@ GqYHjBe-qK'0Uq9ϞN'Ϋ)=&sI\R'52P5MշZ2i; A>+eM 8"*_8EQDEQDEQDN~#zF<17f߈~kO,{5 oIdQk ?DSm7 ~t{C_3v~Z+雷C_3v~Z+\v^t{=BX*["("("Jڴ}ob^}v嶦(#B#h]@m@謌x;u7Mr.m; Ww *Fx<+ z6^dIjuəK5`*T@%UTlm7i%m„VlB7+ HՏSjB* WZٛobe㼃64=”;$#nr T8ZMU!u"Hwcr d-CVRcNZb `@Vlp޲63*YUq讁H꿾GY?]V6p]eG>%q""yӏ5i BB@+:+!d)&_JX׵6\V-/D\^7t)J#+f;a#`5ULJkR _61iu,^ql2 y[@)nĖ ѦZei)vs.kSV ABPpzS>Ժjn#X6uYSIJpPW MPK|UamҶymNHZiܵ09>5;aiմ柵E*}p[5HԻbDEmq$(#\v]A@Bv,]> {x,9GRr^⫿NO<fIbЛ1#(j:֖e %A< Sgi h)͐ڐ;<\%W]l"[ft}_Yma]4R H] !%^0[mXCmʖy$ѷV:zb$QmHr=x-6וKeÉNAR@Pϲp} K?H#2.Қ<躩ےM'{⸆%C%µ$:{RZeͽ-wwӖ#ˊqr HRO\qjBG8]iE+RTRur\Zɔ̨oJuUն }Y^:\!mSIImZ9)+y7Ykl[%GyPFAR@ uXpUVKiϢS'LIwnG88k- blWՉBlUi2ps-{ʂҖH,qZJU&B.kOM5)EjZavi#c*/覿Rs~&j7ߕtωX4! cV:("("("s7if7JZyc٬SZO_)IO"~h9Tܵ]O_LݺOXЬQUjQEQEQEQEQE@,;Q>UEAB|*( B𬨢"("("("(/zȯ(d^5Z|| QЬluc1X+;0hk 9 ,7bSf[УXbS\,Ī Y ^fn(((Q;ʲ(+*(h(UE@QEQEQEQEQEQEQf^>kO,{4ٿW`~V_??Q`ZVspGjt57jů"t57jů"\v^t{=BX*["("("+D^ZwˆZ}aG|(mGwˆZ}aG|(mGwˆZ}aG|(mGwˆZ}aG|(mGE# ;}aD[h]>GE# ;}aD[h]>GE# ;}aD[h]-VXQ# "Ej>-VXQ# "Ej>-VXQ# "Ej>-VXWQ(4QEQEQEQg?=E;dzX+`tN-~I O?:=jt57jů"t57jů"\vG%(ղ("("*6n'TɃ3dTzP3NKcq x<p|Jά}8,"}Գi6WGOU> fvGuZOVc>!)_)εKavRj!i{:" .k2H*۟!κt˥FRMz:Ö>83+#k닸 ެi.$nKjrZ\[j[RR H8 }(_Μ .jpv!2G˛J xcӚ4vzJU*#HK;<ï^*TZCG:0|/`0?XjÒ_γ G֒*S7;qNz q\ٽC+F,`L+m#j<Z6Ƹ,D.z6J(T맹ޤdd~Tݏf/mR98u5XگWN_LMBm z#MA*EabVr-.WK Ʒ5G+?f()  k{QzU#B53tQ@BCܔ2}Uz#VQT-? Pv[pimqGqk@#JvpW̑fZf()K($ jv+:ܚf+Dҷ;yXZ;;\?H 뱦I`HP 9Nӷ]ꔦ]z22p&_#ZсW\8-/˚Vkai܂71*cJ9ȥ-O9r}a*$Vʖq$!K-9Zn.+[)A"V_5We`%$ǸU{3wk2n5lB32:x]6n iCE(lGsIdk8 c?Iƒ:'g~wS4UC Vk  ub @moZ} !N9R"EEv a\YHR[8' $㎵d--VьrP Ur@)SAq~ܷ7[ n쏘5ʛ*B!h%bw^^ A8k >Bߥf"3K6>* De2H8,'zF䒶 VT.VTГ{)I R߸1==}|#kv<VnF%$x䗆f*M >,V 煎9ʶ.:᪢y,2Q}rs œA}H#-*$?:G.W ZTB G)R8R"u6p=T tu4б']~nG5 Q# g% [MY IN3Q}1Z#j$ 1ǟMqn} BOfQŵe񻑍ѝ Tθ'z1Qjii=moL[G5 {Rd= -Roދ`wp()@B08P4dd@*ڦ | IW4 !ԅԽ =MM +Iʖ-}R ZN}UqNsѺ~7GjeKbq<w|r y ) H}-YgTm(!!lCaڔuR!X)ǿ#_-ЯG_Q)pykHy;ĕƀEv'({6RTK f{8=9i+nnbg/%ޭy(ُhj/5趭PS>M$%KXRP@ |*k~?KU"j'i:fY ]pp2vRK[ql@zKIȯk{5NMRJ0@6j%Y*q)ܦ #\!}4DKR '=®4ȽW]i}\PqJ `ztɜ&ll}KhnYXA|< HM)8Č'sOTv=9)mjBև^QR8'9 \ԝ:lBjmʞt[c;Hx8ZMNjeᵲtR_\  I>D2q+K[Z P@kL5\u QœV@3Rs)Oskm{ٍI*|TVq@h8{O>MĔ̹4VpFOEv:[ĚĞltDZDAn8z>c@?K̢NY*mzLdi7 z,ZGg\k{DKc4JЬǟ; n)Ac}Xeׇb*t? ኬm%nIPJG5N<9*҉mq"nq&/K[۽=vA))*R| KGȰط[#%CdYߝ@ njWߋiCmpEQ*=O0N n>XtKܴ'jb岕$+^ݜ|sx*gE3n!%ƂJA;zb|_@杭.\?ֳ%U gKyuy^J)B9s\ϩ˜0*sCօ%B sc]G25N*^BN^~u)sG9BX㐷avе:45Ҹ tƩW qAu!ywgέsc[#̞ƻe_>3㨯;9:m Be}o5iЗTʧAJJI ۓ#+V79"KCȳr[29ꃇ^ ^)Wi+swCݛiBRns#Ks\U >ߡ(O@g>dռff|qa9‡Jѯ`KxEҎ` TtG9Qo U(((˳S"qJzc٬SO?EX6`U} cTUkZz]O_LݺOJn;} RQUjQEQEp-;ѓrkh-A *R'Uv!0#љj^-V]1յ}^ #sR. a! O<A5-Ճtp"4%)ex'Dܙ{RHqһc{yޥt}H}AH]l԰|n$/k92Ŝ[mK)Nx 9 .@oC0b",3|1ȃvp[dFܘ/2]P”7p1қnwFӷN2 HN𒒑]*U.:ݾY_D{`$~~֠Ek2Q.5Kt@p1.SNFwZ4Rڌ#07puӪtj$Lb2#NT6$()>9&Z>ޛpjv!:PXiх(tj$NC xV,fSt~ҵs2ZyQb?vj WG ㎝9LNr1qeԌ`s^qzDTͲdʄ}P}P 䧃kUi^ecSqCqI<ڔdq.mUeGJc[w u 8e1cVW%C'D adnsdla8vp!k%N-D(񎸮mXgcpHSRNـ29>>5tzק]qlznnq%a=Rω&@F}QljUȹ7-ԳFmr,N@*P nd%%<=pAՇ̵ACn*2l*ڧ=PN܁dq֕rr,]mpJS*CiEr0;9OڕDETAv.yvq,pBӌ*mŘL\ݻ\]mPRE8Gx妮}I&64]Co':}'>ݮ`X4^wEעۂSLPa*¼Dӧfi,hY0y8 p I;*p+~cai/R0N+=k5tŮ,֒ͨ(oOO|jٱvy_n^(m nIkQhX~k0UB ҔO+8"i./H $Xl7gnymv$a '>fڡ&ISܭm,J8GLVd3X.r̮(grFzWm"Dk$ etV> j",WDN%\oI9I'n8opȆ qbz\2peSn ݸ޴w!M0 Aq y&pȟjYf-%jY {-GW;a6l@)qVR>Zp̃d8^%'pmt :!+׫{pZ>2d6- kxyS5:pZlv7:ӟhF<+e vc]} OEdLt;l 1DܡkBBw%dw}8}q(nvnr8}wK2^_vǸr"rESXS>i(JBLV:.rCiϬN£RJOU؟h }Km"YKDT_GѲ(2H㗞 J,9-$[z+BڴIJ %]u I[V0ݟiI rOd)E8UpQt9zcN8vU.tp;6Rgj{i6-oᵕ-^<+8>SA)a C͡n롵daX>ttS2v)i'~3RrX4tv]M6ܶV(ʔOBl ?Ii]w۱,KK0H-@J> '<ƭC $I6'4= %m j JWZF*sds-zVt4L$GBٿG¤*:>kد((((8N=1/g=[1 cTUkWO?EX6`Uwy UۡT?\-~ۡT?\-~Х/:=,QEVEQEQzQa"{;TymCh=+V+8 Y[a)'q:H]2LKt[g}n\=e4{#9RC$g& [mzKJq<~kJCH߰fŊQQ Z}cN諍=%TYm+vRsx=jRƞ~1Y . 6ŗ o%A+RQQܭ8 % x`ƭ-1v njϾRg¹Cq]M*3?A5Өm3&nn$)a+sRzAU3Y-z@QS`-kAP)'iT'G] 2J <Z=\vnd RTz2sXt.#[y\9%Tݦ=C2lmu_|W[@Oi٥dKnb5Ş#nL5dai+ٮ̡jm(8a鈒^'ڢ)>Vk1 fV*2 I%w3L!(ܼ[-˷5-2z*~Ñ}x3!<{?K,4,F%Ocʰܣ&"̴!6:``{SO dԑX -W Uuo,ݤPpjAΣ@v3~C )VvOTj5J\dLJV$% $ gNEFtM BBTH9c-YP'k >QJ>7a{R{@2ЇuJ,eJQW +8*_rʖ@j ~ ;H=G5Za&܋{ -V w5loޕ}@o>-ȊsOw3SEp2w$yǚޯG jqy9d7wnQ੒w(c3[׷߷iYneRC8=AIFStc>\i1by%/ϵR:WHcl/+//vpw>UD)sWRmFGNI`%SH @ p8J_-V_q֦Zmh^ dn ǍV UpFmPRސ*OvU-)3$)*Vt%J5N]`AmcQ<}fFiJ#(rGUE k:ZpI&Hqivz)+p>n-a̭I9OwJPEDMmeZ.7$rRrq GS4˷(5d{'c(- 1[>TJU)o~ܘZ7 P #+GJ^×A]MH/1eeCVWqP#,S7{ O*o9iϰDFz4e)=y!g{7|Q-Ïi%HI뷯JKkJR 7^fhbno{\Q#\X-qKZ4}E갆֑n•*Ki[l8Ho/tҷ-u'LbptL}_X-4TT8~ڀ/{&1p))6$N2%=|FM$zY8IF-4L};wkjFx;O?XzZiԺJ-P {.#9+*?{RǾeq%Vg 4`kl mOr\/FzY)RG Ɣtfɉ͡{W(Oʥ*,{ٴR@-RJY wg!H3Uԍpk pGTGYGTZ/(((8N=1/g=[1 cTUkWO?EX6`Uwy UۡT?\-~ۡT?\-~Х/:=,QEVEQEQzWPJ#9 w5B@?SvF J$d)>a[dEm8A \E0Njp9%{N<~P tB}>eжl-[(TZ:Qǻ="c ڭ1 HRbfѰuzxԸklIw2\\uA}خ E T;nYX*C(sR"=wZ48qY! 5GNbkZԩ$yU{.[r$T@t!DHz+sdlkY`|ǏZdJ8cjgZĩ-k.λBۻ$E[hm?+gtfa[Si pI}4 0ЬgG8zu'g/_FJVg} vWGm3s>xڨݟ.*:;[$sysSfn4ݧ[ 9D('G"}.K]^HvG/I%=]9c KOLGr26蠎n4>6\` cLUwj2n%Y##Ww[{jBBZ|@9-5ie)IX+$aN-a@}RMzx#siKd8Ym&=jLV <*6T}oJS+SBlmCJ#䨨R}|=t͢>[)Cq\~M [0#k#16AS \.e%i)m$}w @*qjX;[pnH"[,cG:ޫZ By  jW[7auD;)NCi}. XNHNyuS_U݀& ه7#!cؖ5U7FcDtFaM\Z;l|M<Y pmhwGMҟR (Vw'$=xכ#YFoPG-Hˠgl 5#NZJ kS̩*u I)NG cUM:8p/}IgAֿ+mK ݏ#ixB\֖ȝ ܹEYdJqR[TӻGAT1zIiN~5 3$b\1s` pNmW>a|y̱knVieR~8py?ip+uwjmQk^ͺ,+'${QR Y~r}YSXp9(5$QG*`/siwqs%}?&D@b6eR%HIӜ=ՋbKLHmO8U5Je-R#8?T4<( hJsGJ^%CǞ)S>%̎і >gzӎܝMQ: <Ȕ[kIZ@KWY5,'hYl#J'G_}T1<ִL-]AJe7Fz+̉(`=endlw KcܣG8MHi[n*l 5ma넰!*qjQ:]j6y=hSjc8⩩_j;+陏#\QF)SeJPJ[7K?>d_a.)K)>ʱ*i0ZB8PCG9O5ISmS]i KV 9;>5lu 1. r9 }Ͽ<_(nd!`5>+>(yѲ#H֙_5SƁ٢aJzS:ڊ$IzgKeZ`$-)т+H[ʍHK,A#RWM%VI|{@ojc#1SVL$we퐀03P7Q#!֘jN9V<(Pq'iWC}:/pl (F8R9 A]4UG(.aMT];8ftF[3luĥR]맴m|zBRV􀧒<<|xlMiW]4iB$}|U$wۅSe ĥֆ %`r !cZ2x4".~p_J{]jxI*rI?Ѧ=96$b4Vecap~BG\x}/٠_tp.D٪l% x䏇S\s,I1TWtnG|m%']Dݔ V*DU!E0RXaWQKBzY@*n ]%Bd8$$4lmH=Dn/8..%W\4+U"V2'+4Һ3pL6? ($C_ZKD;Q/rBMM-4%8#OzDzLE ZWFqYtw$cYGTGYGTI^QEQEQE9vqJzc٤^?iOL{5 cI Lm~X9TC_3v~Z+雷C_3v~Z)UoJ^t{=BX*["("( =~ӯZ(RRI>j܅ * nv!wM3#q$lN:jC \8M=q~fU)m8+i!^JG@_" ^F%}.6&ɀK!!.,)\ %+ m χe-SJc21rk)<-[O'Yk1+`\WHVGYޢ<<+lfKM8Q[Z[ ys\[|3g`.{% =~TUi-eu%7(9F8+E\ekXeHpc#s]ȍe1}69rRTIML:$^*CI7{zhpش)xW.gN%NJHI\RFq|5CvF:iJ G8蜀}WY\ײ\0P![J[RP}chWv(2d$WqU蛝Q1L4Dnj]ۆԐ9!'$xUM:DeAIyZMH$:BRk';:)Atu͇d-Y9˄g Ǫ9)i#Ir_JOI3XX}(|e,H]) ƻlߊ#\q Egzm T᜜|k>ބX9Be+LZ~WMqhY- . 0 ]%O'py/ӷ]3q)l6-_P $zUOfw4HYs?et=if֝jTKoZWK}ҵ'v}ac`)vf-s+$}Q4lC޵e ^1ڒ=@og54Tw芻@-V0Fvp?@OS^̴NɊJybKiVĩG;/OqIANYC\ {,|g151Av?dijxMK1qRwr 3ȮavxM4 )T$‡AqP-ԛA%o70Jw% 4 G zGvoۮO]e;nm9rBR @T@ݟ`|=4ӼOΰ6=XbOg:>e`D?Ƒ'#ssӏ_P,JюrҜ9Zns.F64pqw-N@O?[DSqSiHJ|#<Skސ#Z5nmKaMq(85d:Z:ۡ(X@ϕ!Ϲͽ~-{q.ԏT[=ʻh lpV Խȍ.&k 9;ڽq 8_@-)KqaԲ+I͊kuW8A)*RP$m[wh*qXtsbZ-HoGJQFWJaK q\]k2$G{c8 cs⪒֬J{=HDi y]mCRx$`qpY, J K=ͦElS*(PޅEIPFR@ -(_í6RRBzOJup\wnn{s+vW:y7w4ݾ{+ja+Z! !%ߔH*M\-1f8. Vw49g*>5t}f(}/%)W - :>sl !4TugWOTRvWaKHڒx>98͵u 1Jcךj)30IL"J!ֈmScH3iOFm=EN bX.6Yx!IWI ')Zbt]KhZ_iI#<PȩH18;.+@d*Kme Oy+I[OL,,+;ѣ|Tp/MmUDMC&:0V@=q§W2&]ZA-w"7,%Yi -R*>Sq4v$h0%K.%YYN3ri=5y2ڒK.nr✡vSb,!gOWAekUnN&; +Z@J $Ϗ!RPs)’$GON1z[XmCIvT+gơQa8}ԴVԒ K 2>TQܣ֛ MgS}wH=1r>Oۊ^#׊e{"0wՎ7 'gՌd@B>XPmDmVpyj]3\)f;P i( n( $c8<9 ]-b& ^+C6xV$$LT6kQӵ28?QvڜMM6]AeK %wcpJx—u]Dw 5&!BR@ڴm"|SOќ5v/8BG'- {Y Wiw ZnÈkqڃm'dwmX˷Y-FڿpKښڭkrҥzIPʃDNԓ6Ae%,TIR*>sӑ, g*V7Qz'fE{iayTZ : u ս%JukpFN<Qn-8y"Q}XCN ۂTd fBS \a;sf"$Ve£vSgmRu4Hڷ#(d(~5h\{uHyn 0P)JQ<uu]})2K[[,A#>uܣ"AV{$ܗnlcSlmcFSrJI >D Us1еM 4JR1`M46W ƳCAA9u[pV#nz'W# U!+4ؚ̂aH2Oߵݨעg]/1zQQ?iM0r.fz0fS ےLx&"ffd=%q-èqAGpO\rx#ex6OsZ{sEb%8Pck\ݔT .'r#$?h=1 kLk^*:ZhRCwXQ9md&o5>Ÿ H^ SWv>IQJJ3)l/iO҃QPױKjNϬPzJ?F*bwiCNc7EW6=ЀV 7Q};5OWTXehy/' ~Trsb[A!AEݜLjJ0k_r*<̕5q 66 +C+cHJV ptW- XBym60W$ȥV,C~o]btg٦ݞ)!ĭ9lܫ9V3 ˪!.yh `"1hI_\\f CpCJYq%hP9];>yWm#VB'[[˒x8BU r3R! }?H8_<=kե mgv'xN*Jު6%%䎾nEr\n} 8۷Xx&f78MؤfC#zZVimHRqeEBѳ9P@!ܜ۪ph8ƹ_SUÜ^r{>Ŗc\kԥ |~@Sj<~htMM*vhJrlKeϽRJCjkH9]r>B;pbcԷ{V $ +ǯW$K:˩q *IH#7VfFjŢ2[g=:AYJR +Gծ"ZOҒд ԰y&T\.$eY'98'?n|MGJqC 8p||| |QEzH=m8&kHllh* W]`QR ("("("r;dzJ0_Kq[c;HQP? Vv`΅s+ `??U`I6Xi.@2@Hk_*n;<ݪdfݪdUsqRP(VȢ(((((].kD=)\ Ӆ p2+&umŸKzع+os$%JSi NPQcs~"a]Pfae,rH$+U$췃#nYTن.cV-Cq=UbEoe\pa-C[6PK VrOSBԒ%Mp7̴}W׭p޼x(es1m.WtXX[M,R'Mey!Q޴ɸIkie*Ce#wRO}F(v{Fb!IB[FvX*("("("("(,V0/Y\ũL-,bm̔;JA!Jy(ƚԥ[TGK̬]a #<1čWqUdB)A6%TdǸ!FKԗhߘ!9})KlS $)eJ8'Z"djD0컓wFm_Ps5q  @".!Yk1iauVN8hbth yDAz#f6!w)P%)ʰ ]]nܭqqu%A))IAG90&&eSM3T)ӫXq9I)ibQ-gs"-2[K+:tQW )ڢ81um)iM75ofܕ ܖڐҡc~H<qEv@AI#c hm]rf2؍mG"#Б A8FNqv|AMFm.YhGvcp=|6yDS |"teb҄uw!,rxuZ>8JE.(l+e.)ŅzĒ%9TWEͿߦK 1s>INI! `qEQEQEQEQEQEX5sU%2ɭesRz8D $v8RX%=Bڌ$7ƮoY˂JAm!9JqalN)Ɣ% [eAEAǘ8[_)*R8qhZNBpAʝm~X9TC_3v~Z+黪II⪽W{× *ۮpR|f|iU]HVE},ߔ\=U7*hΐU=l},ߔ\=GEٿM:CTU4~Qs~sK7?GSf6SVE},ߔ\=M3ɜ|{TQMwqM8#Q|&pG#QE56L@GG#klwʏ@GEw3=wʊ))gz=*=S]Sd{Tz=*(ɜ|{TQMwqM8#Q|&pG#QE56L@GG#klwʏ@GEw3=wʊ))gz=*=S]Sd{Tz=*(ɜ|{TQMwqM8#Q|&pG#QE56L@GG#klwʏ@GEw3=wʊ))gz=*=S]Sd{Tz=*(ɜ|{TQMwqM8#Q|&pG#QE56L@GG#klwʏ@GEw3=wʊ))gz=*=S]Sd{Tz=*(ɜ|{TQMwqM8#Q|&pG#QE56L@GG#klwʏ@GEw3$%^/W \J2 ./resources/demos/grid-view-background.jpg0000664000175000017500000026723113623376647017174 0ustar dandanJFIF``C  !"$"$C" h  !1AQ"aq#23BVb67Rrsu$%4FSTv'Cf&5DcUdt(EWeF !1AQRaq"234Br#S$Cb%c ?u֋~Eܮ &ZS1,2w4J DOҿxW]iQzgjAZ G S$ >]c$޳,f@etW~oJiWO븷(y uV֒ ނy2bC2gJ8y%HmNoPn *ۑc#5Nhs7R&qTh6G?J]o-nuyZ/@`JMm.HBږ(^RU!sʲ0.ems&F6̹,6)'`e BA!pNIgDko GhT;M_1?~~kK,JfDqS -p$Q넦Ԛ4B$\Ύ[Sn!)-ĭhp5hݻ_j/=ӟi[OJp~{nGݬ/CmJ-Bm'p@SNQC&pܞ\WXz#͚M/?bW O飖8GJ]IhKMmP$a.)fd+^sQw8In61ߴw*F6]j ٴږV};~KUN ;cQ'H)_1?k㦔ҿxaWNVA+ODw. L#ސU?M+t؇~C[{jAMDw(T#޸\K 饴RKf4ŹHuئ;f^+jbCSV-UʕԪR˃W7Lқio -kz_߅UrMrMup\T3WE6ߵ/P}i~Uri ~+[4 ?sK𿪮kXhz%Sy[\/?>t ^@w~+/k_߅UsY\)t':8`~U]/꫓s;n/?k_߅Upk)t~w?k_߅Ukt//b;T \{St//wU\Vb١g^/[}~U+T@w~)Mt t Nof;^?L_k_߅Uk|//pOSy]_Vw״ L?C/j{P{Kп{؟k_ޅUpEh^@w~)N'wUZ}izT+DRhz'gk_ޅUo_RRhz%StyN{_k_ޅUl; wK𿪦ՙpOStyN~t 9sK𿪦`@{,=gUoK𿪥mkz[߅U0 "JK@wЫ{ʐJmgZ/Ti=tΖwUQrKAU/H3g=tƕY1Wҿx!UQ \h#H*e0쇮ҿx!UJW?ꩫn{in{j3ODw)D#ތ֚.3ӗ,TBS5dJ@*Hi>Dۥlb>PП<:G!uH!ZR_`LL[*B؂Opw05]d-Ȭi>D۱J‚l@ F}X8G#) ՗K#+JjKjJP,#. ㌗$R>z$e3bfN MwDbLO- 1cVa(Q䎔蕣UzҚ\ ]hݤ"EyŭH`s yX-?ULHލp~վ@em~EF7G]uѧ&~-}5qz<IuVἐJP/Gٺj/ vr.wݥn}!DIRSABJDCV_ i'ikp206dZnj=\th-ބ+z{Vm6HynуMx!WC}%߮Kdm͖Yoʖb v YǬBE^j ?nʈ6փsou@髸oHF-TcA #94ƍ!].3mUYF’B60 $e:]_%JA~8߽oz5L5, J?6T5hr::ܥ*k 6OO qN)ƚq4L1M}MDx_R0$hyܲf9]#`ꇭz~+Zֻ*$Tu=uoZ^Pwi;P)$)*J*J A{RJΫص8餯?:$R6*oTT)g \uvC2P e6,Ϭ(6fL.sF~2^IZ[j;m9@g9<j;$ΫFͪ~?:E-= Y7Kk櫔wu.+h4[l- OwmRT dʹ/,Pd; fVVw)ED(]U~āgRΫbIh&/siˌڭ3)#;pS1 P=;{O{sR#57riKL$^$nŽQ?RΫJj?bu]^¹ٜ7gaDYmѴLYdEcՎK-`)R8YW4kD]Z}t?Se PZΪsܫKCJqtDjzuIC,^ڃN+)AW4U=r8+Aj,ڃ&ϵYA045] ъ:g[Loz`w]nQ>Pbi5*Ofr=.^?BU( V}^?־:r ^Kju0տkuن]}0;40jֱG_R[z`wZ=ߵ;tu.9$Gg[z`w\Z67o"i`W<=/^?־zˋԻ@A3uky4z;)գ^}0;ezv>ț)0idof]}0;Ku_Df<]̫ pEXWz`w\+XMsc:WgyVVv=j!TZ\?jpVs%z;'^}0?;ԃ^Lx@Քv;)kׯuԫW}޾?шjwxgf}tXR]z`wZߵ󺛔izaVʮ@JuwgԧW}޾?^KjW`}JuwkQׯ_Lr/L%ɵ]\VԣW}^?־z(\U*hvOׯ_LFmW@ڻzMׯ_Lr/L%ɵ]+UaIugԗW^^KjWՅ$kׯuRM_z`~wKiza.M_nIׯ_L>z(~N]:X}^J *ޭE/j^ifȗ-)e} :[Byt$rs|qB*4ie:nj͒Vm+wfq3LfwʙDU/Q,0}1V>Vw(gT 1w_66ӽ[iٙ IʂRT#E-ܯ47 v! m%[n{TT~9ԥnd@j_ u&/߯i5-!12Tw5R#BYsyyRs= hN<㿩=kT@ %@Ђ4\}[:^ ƎrmV\k8aI9O\AKZ-eڲer0v4xx'<5?ݸؒ:s-V_gs)3STX}`I#Ρ]^zL6QmQ_ *Z C: ;s|Mӡ\Jm;a2@y* KJ;ӱ9F9WL}_kޙmwRc,Yh+ˏ1tzkCE /~ ۖҚt%IH(bsNE 1.M 8ᕫiteC$g#hHEaalw^m. }%߷q9:S]jmld&j=O8nˍ{v( d[L/!}\QyWvTJr<`ղcv[W(6%8ӧ"R>S8ﲔz:Ԍ jd66xmHYq_LV>r8_/wԦj<7wkMp}Ŗ۪m J@ B?iBEE> 2DTI*) RTUl̥DѶ{|֘ ۴T [rmjV➾D63[_u-1q,FmECuL$MhAiWG!{e{WQøfuKS">_aiqzo_jKBXLDˢSj% d%9v8]vcm`(khq퓵yl RW!3Se8OLc]Ƌo7Qɳچ!J%ImuJJjܼ\nz>md S 0/ C)w s/?;8.WG-k1t&c) }4P) Fq=v3?O;vGzc7x.F=&J >7׭枳}a:\Ke}ۡ XiETov].b#EPm=MϊLXaiyԅ,>+r}dh%;s󒦭J ()H!# $WA -+?MCO^-ZԱp&Grݸ=\m;KiZ%i%dIJSmy<pyԻ]Zxv5[Q2T$ۊ//EMؓR;\m IPxyחiSNk7n%=mmHw]U`+<=cuZNĂD%zr{ q F~P,2u?3o̕eն:rrTPop7$O~tEMzfmZTh.֚JCc%#$e{ܯ5 }LRO~o_iuYRjW)r|-+?M2;[ J=[W~υG]guKa\ }Yܯ4˺l5+o_k>wU-r-+?Mgr.곺ԮS߅G[W~eVwT{ܯ5 }LRO~o_iuYRjW)r|-+?M2;[ J=[W~υG]guKa\ }Yܯ4˺l5+o_k>wU-r-+?Mgr.곺ԮS߅G[W~eVwT{ܯ5 }LRO~o_iuYRjW)r|-+?M2;[ J=[W~υG]guKa\ }Yܯ4˺l5+o_k>wU-r-+?Mgr.곺ԮT_i.MTzI!uCv[5B=]ݤ55I,Tn_#՘@ʕI1Dq?ƿ&׮hc~MrvÚ TI`!dl_?U:hZ B(s jcn3'fNy ¹VS8iKvLڢ23-}.9ll8n;W% Jݻ FyӓNҶV0WNJjmxNr>tv-Fq*ђ\gqdylV/mR˳JS%z/4zݏ.DGuIB㦱fsxχNV]ZZvH[VCN]Y|'@)}Kw[akGtC :tS>>j+ ʅwC Mkic]:O{V[[rky.$ŘF BN$?C)1ZJd2ķܔ̀q1>kA*Ka{|ki$cde)RnkekLƊq>^.ZHuK^~ԃQHj?<[h(BHG*$TDtn2pkxz&xo]N8?ݫձ4,O\u#%**FV;>1r`~EkV~Bkk Q-6ӊrC;rWqN&q<G-RTpΜWO[$cm=kmk̦iŪ^orDK*JB`qm;/QȺ4F/|s7kuLkv'+VKtb+-mXwVmBR* OZ)G<=8U).$Lj)WųuG[~!?^m;=4K5F;xOҕŔˠN~3յ.Wbj{{uQBJ LkY5=DEGhk5gڵ-0&;x 2Ǘi(pY\z>GLP:ڸ&ij*h/:~ KngұMJ[t4imn |6ԍpK6}(P 14HCUVGtR4/`۹ыW jˌe#ݎEBq\!L //o/qT@N뫃nM/)o8p|Vm R^ ou-gf WkpI)A'򦧴:dXnR2C"۽ں,r=#XZ*-.J8;|EcgUc͸z].) "K@Zj5ڱY%iX&[H N>Q?7WtA^Vv ? s\-+%!d`^y(XZ-UMHN [>GFB%K[w oVVq!#qCIt{*>S>F6H,^e0{N wbh ,ĀS- *9J@#Ϛ_S]nM& S6)Y~jI!h2U}/1uv^kʔtCJ7eJsb9x\-Lv1#P(P[M?ٛ})\%yӂ9>[T i\>Jw[R".RV&ڕE# P6oj6iz=beC$G  QߏuZ1[e|;q'*v`ogU_O +ݱ&7#* 8ML6E=tg?E-ΓQ Z5 {hKK,쯽d\-jO1_ x SmZq;oQN=)6kLI FS}4rsjI&$.߱MT<5=GO*F>ʇ lu)0AFNZ% ,BH$sDd^qɨĪ$z(`--":P3Mڸ{Cƨ}āR8AK*zev(iZh)\Lg҈R+ȏ頷0NNw:xӆ0q@(c9Ր RA7 \&c =˕hk׵rl)]VQ@n1˛rSWj/7a)@`^jfz:J٥Cx?>MQ]7ןkzu{ԪFj>4a;H$W̪Ch*SH{)rK;ds [k~uCl.=N2H'5`,(zl~u [ҭIVA^vI#c_^ƒdndy1U>t5CP3HS I_tܑ";Q!ŢRiFr~3nU!eQXr H$SnݦH7XmTxnIII sֈWLۋ@E>džx}uݢڒ˦ Ki\lGT G AP\i $LnyU7xh%*@m @@Wܞ*:٩էP ͻT>v*n$6dO 9)#(mMڷΈζr9]n)!2C~<=QQPE]S5{Iw'57LiDZ޶.Ԑ ^@PQ<饃_ ^V-ʮIa)J}E{9`;{&ޥqp\/AyjW 䄓i6ZAVW]}Rө JRrp DžC< u:w&cvoy䅧mu-ag5 HywkmǒzUt'\_Z#1I~lBRXdcZɇrzrSݸ2ڕ-(X'Xan| z3~)KP[IXnVjsdqSwEI|,oҙs[ۢ[ޝi55ٵzSK *C#P՛O"ݲF{bDtv6L\m֫]0q7mN!+h_5D!0v.V )Jz8‰al;S|sv5R/Kj=njAqNU-(M,'m?ٵX1 opegojܰBCu$,lHI] #Ibm&%5I/6T.8fљ_v&Xiېi}k! )]>~=̻t Ԅ A& LܐuRJPlI#rҥGi!(i9:FRY:I|n!5kdLx e=o=I>d/\0Y-8By.2p#xUPɢ?jVIfCqkZOx=D=E\2؁ ݨ„&BQ-LJxEY^-ƛrڗ\wgFpe9D^5s DlD8K+R%j>C1Ѝ4%s︷:5;Nʜ$7WLkw,j;SϞ0kʪzE6`%(~QZN\*!)\{KӒ,7閉-'RvyЌQv Aqe-eĄPܼq~S`ݦVx][˷3-VN5pN(j^ AmR>^*($8BH4m[ޒd``>~eӕgjk=P/ݒ%a@yN3I/V"KlywHv0>MPV ;RO4e۾5m?b L,AͶg?{҆)K-HJRH(J;yf=ɹ^qy `Q5 ح 8RAmc M&YFۉ|jhj*& \h5k1- Ze֐l-HҔ/rv9/OvNU&bD)fAKIY# lވ*sRdmNF˝BUv)ڣE9h;[ s44dǓ魥~RG:Md6>dZ`q[g~kۚMP;,&=N)[ qP)9Ǭ}MYGXJlc'1ҫ-@jOOҚbX#6[cnZ%AC%%|UL2Z%e'[h[?dcq ()&>-|&%fq%6j=2vEiJvoG[vRƻ\޸qݞd g%E- H8Vz Tko;Q#HЅ? d$Bf ~ԖJǦRP[<(uImPI`d|<$:Sf܂7#48hcnn>WֻczwHƼ%)/- iU;8nƫte*ʚRHc3id jS@-l h5ly)޼ԟk߸ߴ9*k)98z}VF2mmuA6~oAA:TcTݻ[Mb4q9@ z[TUYZ.M%mJ}ʿէUNOb KJCeg'TU4#}MS1صّacHXUf##w )?jd'fH&.RžI/K-w %U1\ FO\EJj{&m'2Aj6ijpsV7" kemo.>^)_)Ԗ2Ն:P;+n{4՗ (kqd (~݋n@8y-(u$PyWΉv7_iI^IV]3-6;&#y!@nQS) 2;l T!bRHHaGќJbg\3"((hپΙs񲤵j:r?s} ) mb\!c)^:{毹=WsYSm;+nTi!ȏȋ&LNx.mIϵZ"JꐔpIʏxKE$2Bkp7kE{t d2 [>jig Nd2 W_M6ycGO&k dI7- T撕·$GMNd ;sj;9?*]ETxriV8<8rFPIi$椺)`@9'kFy<ԢO#)8Hqd2rO KGـWχr^m}m>;/Zެ>r]^m}m>?+gܡտjWM/Uf_#ڷ["{}]kz|_b*[_B$_'!ŵ8PVHODE~G7h6Y!??&GK9-\Ng8>J3A 'UVhj'IWxUS|TsLh Fi.\x%GwMՅe[piԲA( ihIs4-.I-4iʊA!?\H%װ?c\ŽwޓrCHy`aIQ 83>G H:V%)_E8=~ݩG(mtLǔ#G)6_fAzX*qY˱NɃ5r[Rp-8"[.:RUQlycՖP-)mcݔxˈj%CNs ?BϽ0ӖהH # J,@eLs߇qi; x<zƪ5"7GbI28 <}w>6mo*Kk#fy8ǀ}+ w;,fI{RwN_ 8; 'z wd1r`L\-PA!`Zr{kN&X4KKd!Jܕ,(( B}WͅhnY"waKPHV3(W:Zᥖyd!Im收iAIX"s|zxSDɓKv99$FX!—PRO8RS.8qSmN[.0mೀsm ZʫY#~Df(i saG=D^Ҳk8 v>4 BUr=(rGqkφmwxP}2E{RT(%6< Ѧ!6‰@Px!Oxb7 *{K‡wy z8mdwv'4{)26=ޭ`+$5Z#Pd,9*QSx1Weǃ)Gu[O}rODk#Y#y\^@Y֠>kUENmPɢ)ljU:H(R #L\,i% eǒrA٩4Gn +!d@[LS*R㌞=#RCG:IݥsߒFmmֹhq,ꖇ Q^jhJ)}RV JJ^Ns_RzƆĖl!MlIu= 2\H\Es7;lPRv-9Tf i.ha:-VP>2vH wߩ{-j˄+X2%H+d!+Np  Seڮn r˃>mP hV}; Ax嶹q|85kV$b\Es$sNw*g,qu~WQmqls\^-FšDpB##3q@kM=hKĖμ(c~ /Xڦت`ć=硃djM*;3B]NX Hӎ5i}3f-^}-o`T)J)[(ݑ9 \7{&jmR[hjX)u[N!kN7+gM[v &,NpJ !Me G 29Iyŵ$)sf5Ue:Qi^WOI'$(7iዾ r x$g54&nS@ hGP!c3e5k+ҀRrǎ1ǩ㦆eISK$uA$xg*# L)-4!G$S%(w+O$+ݣϥ2ӕNK~We*r?Isv@?ҔSjr|.i=N8k)Zq]8J INS4_:TBv8r}Faη1?} 'WϹC~պ_*/@ ڷ["T:1N?r~=hiOܳCE0A0b(d`m!G$}z?S!$$+7v5 :^L2~Jqyǡ$ |}ʮ' !!һGgUYN~J)ݜg>8372ځ cQxb3RTJvW=yUݍ28fCi $(d+x{)Xur:T ]nOc䡴mx8-RGP=Ty#=rz,4v|YY^^zuyp/.c.. x(e}qiQ FIB4GXA|ӏhF^vѱc6g#=w*0-ЊL.0XM*waƦ$u#OV4VbqY+9ν٩d=ks็[Jv\in45q rOz#EŃ +\3iNq} )sdH>Ӛ{7iL_de-yܩk $tlh]i%iN2rrPxo,.s-mP?eEmU=Lv{ O-dL$%c 4deqQ@ '+Oj-:m;ISݠe'ֽmѨ-MGϺB⃮-̡*$u?1 Q8=t $4vmԖAi)Ɋp-Jt=+oӶ_K&\#G6g֎ϳ!w.CI>҄nR-ؒ(99rEA%tMٞ`rB呄"4DÝw@3' Hq̆dMuuneNK) VB:qr\hPQVsYiZ7[Hj+$a]Q1=|w@-٪B )ȍ1k q66J83VlwͳoL7r7?0=R`$(Pq^_''Vvk3Gөb[wp$plU*=J JԀquaFJhC.[51so<$)dWoq”@\!D+jHPeNj vIZJT7VJGS^˝#(#`@Jy)RB9A"GfbL8c NT]BR{+Zwd-=)ܓ0[ƽucr]6Zsxzm-y52~>To4vR s 0wkյ9#U_+JcǶ954=!Ms8Džz߲I6نR‡ S+*#y pl{\iN##Z:%ho%=R['x5z3EijH4pIǪ+ɺ]u ۸8m۽HXe{5ućf@=sHq姼Hp`5D-LA7%K*HySK!B|Y;_PSCGf.76TѷeL{3~+SOPBծ#Im$z3}9*i)|$HP@z4isJu Fd'|ua)Ho¬1#`)Kj 2Rdm)< ֽmͮ|DdtUoTʗꞡJqT}DǻS-%sϟZ\Ѷ5[DeZ'slA3ԍ \j)~u {C]Tgٳ#©7./J$) 0BBSmm1Tӭ=l敶vqbPV[V $SEC+_fQtm9m5kFќ lӳ>=kK~Qԡe~IW ǏA(7(-z[ W؃|Maj]Rw6} \f[li柊4]JP<X.q@U=Uqځٰp~6& t9;O s_Ҳ !֙>=|+q[Nx\PC%'${@%?rndejsAC @‚59\VKFljɎĆ^qة\آJSIVbi _yiXfB2QϦĎ&I+Rv=%Y ue`kv2:[Ka'*BPB7!sRK1n~/ʊ 6r y'IDӺy|4.]&ܓ%(D'9H'O]XꊇHk 6}0U16 㳙NCi>1)Ld6Pj0jR-Z̻lD[dNGP:uP:+4{uᨋuh##"hD+ϳuJ{q8rI xqۋјiIqi8FTr7`mA|ssq}^Q; V:q}!ͥ#S{n;isl_%Op)&Cnjp82CLJqݩ+#%9(z3KƐ۳ގvD6XY S74Q'*'cxmrnqwAKM3;Cw5ʑmmF_*~Kf]❎k;UdOc,2InSia8YīwiP@PVA>t+._mkq壹 ʼ5IQ+#klwmG8) e/{X릝|h KBbM:$͌Zd6QC$HL%v{1$= < &ڕ:wKh}T!X½F(,KtRAd]+Af8Zf[ 4@Jde>P>9 pC#&RBpֈd>PV6i9>'9L55XOzRA^i”4Y ZHZ&te(9 m FO@Og?d|_S¼zGDd8xom$k}ԪHڝ1fg!C i^0u[{Z}Fc:  =.UW=`8v}[US?5{#XǫoڷUKEUR~f5UZ c9{Q=b[() PO#xqGv7?!ҢA cg(dk/c ԞŐ.w!sQL"n&s_ȡx|e+->\|&F~o oXSGiH,IR(Fqw&"M~hՓpj gAcJM2nB1vZVINϕq+A&7={Ƨp {>MK{ԗ(΍-*)Lb|3RVR_~@IV6@ ~m-CoQax{N&-)%݅ʷ$JRU|Uj%iNppA>I'?!ڵm%\ q-=s0Ìȏ)yKm#4wv6k9DzuikLg'p!M:Y*h 5N'Z-7)+QPAdОAk.mrfсtGbRws ${ ~k-cx>ULN881AM$.ͽZRfǍ4G%!]RQ>v Kmž$ YZnj C37½F>5C&{nٻP汵Touwֆк͓'sS^$z#Ɔ]K;iGu@- 'ÎKs)p Q` Q 2ʒ Wj߇zR&c^a" )u=A')L:8e:D.\srK%H@qAn8q㧾7CAdף[ʘ܌#;HSYc}ʁRCgZ [RڴYAF@'\ܭ*֢-M! JVx3xR:[f2z⻶BH=ssH1}i^lwBrBW'| QqtI5s, 3Sw>oii6d}.?IWLYvh[mMq*7X8m!)峵!kᵞ<~j*kib]`N$ddU=P;q.JIJ]6R= uקai)mvչ*܉{BvR"Z[vm\Y >=)Mj$NJTlM&+lyOs yu< m 2zS>92iQڋJ{18DQdvmϷɔHDڥ)VO 4AX,3hoijg 1nzn?1ɷw Kq)m!9*#=v~2!,O9eNGəWݡ.mcjqWhSqʂ'RG4^wn\-cutu@ z55`p. ›pquۈ+\&9 l'ޒeX.)~@9q>s rVC;g_k]D( A6GaOqĉ%EJPXBTV}Ei谔M-{Jٸy:)j[z,2ZRT9QLZUu-w4 gOD5={ۙy sangә<۷}}fbR[\q.p=+Wj[z<ńC2#䔄+) ɴ]%0 Nl8BT~)#W/j*Tgh ۵$'<{f a%ֺ1aœhwx^U5b-kQVӽX2z(0ޏoR$5O?ܹ/q $mB+NF:nGrpb“- eL8dvlq̴ ok[ꉴ,:V1 `ceyv*袐c'6_eU;̮epI4VCppyi;IˆfԧqPj6>ڒvu6YNr#vK2( r}IPRP?tɅ((p>_Mf踌pB8KHF4? n)I9cϪ8=zS$WN H_@3M1Z@Hr8BKd~t vTO#8A?%q,|[EXt71ƭ R^a `9| ]򕼫9vSsн8 3[#Y@]#y(6icy~1~Pwr:=*: + >G5=ljCj+y$r~g#O^Rb.`acLj.'L\feZZw[C*aI>BQGvB8$qAa752OjyQ:`\P RzEd= JԭY~%u:F ZX6pwx`}f8!YIᩫ-٤:NlW) @^J)Gn#g,mN))9J;.aA^$7Ekm@3oe퀒6#/Ρj V%ԄI!jR0d#*'][i&P!9 `*,(g$uN]ο:HMGp$>\,Q۟7 ) mMUa qf#rp1quD}p}#6[$`nϟV@ ^yǸ8i[J9 'ьF{Rϊ̕`+Κ"٪氋(R}d~H#U9!K=J*'Tַ5gP3FZu7]%2O1m+JaA;I$ 8R#ڶgz1441K|L ;9ʎZ=R4QpC-K ʜVKmYXBeِ֪lLԬzT[ I@]*yr%.nK,ǎ:bδ 8{MI#}ۯٶT\T5K8:[Mow;Gb2h2:ci#b.o,eM-oedvG'8vmĭ@ƜI. 2HIBĻbObT[]p~$2͇${IYplu JݎBz/i53=B^\ᶐJڐTO{~ ge>ԛz! I酆0xsFvu6kVAf2CK6+[J9#'^R1Ťc1ݝnm+qW1-e>%Γױ_z z'|m##q2qӎsQ_mf&D05ƶ1+nЯ(<Ʈ𬺥.q̌4$7m!#p:׺[3{$o57;q Lr+jJW8UN23^ Ghdk`jIld/%S7B(ܲ<`yPNܘMlBC+ c$;nxu֗ko}+dJ*bMgWn`#K0.h (Tfq-)eU+pNMF̵gԒ4۲\ÊQAԁCLds\E4=%dU;}{[Ncډt6DVKhP ٩}0Bt6Nj$-JN ) q ) ϝPq&ĬI|ҳITi3sNz+N? 2x#H;5fh;8 w#)nN2SG9NsK $R 9'ze9m9w` f9m>̜xR\Wٟ:Aeux5o?_=`b8v}[UQZ|\ٛVY%e@|qxvWjiȪڙΕ4QZj(dqܳ5FѽXG?=ɩ|QǏkp;;55ägo.DWGM++3i#BϬʼ=\:Jԕ=q8HNs=SY` 4p@ǎkZ $rOD 53 wMeNI-8$ 6!i 9*899OSMJ|r}Q9W-@G 3 V[>Um= 8yq*KK1R'h$'ƵpXΦ水IHǺiI\. [Rpqԏ*P̵h5 3R|d \msu9]be%Vms>ڹY[wB=`snTeJXҭL 4HqH/Ht$s[f^tRGj6>$k MTB _#3R*XfBBJdT$t (9݀q֯TLcnɱ $d6vT$9K}'g.F|b$XBn*b mX<>br'!gNJJ 9ɬ#i9 R aqN`s 3Skd>Cie 7v1ԑEP9)*i3Ka3Jhl+L' J~WSF[giImuflf6NC @T@R|0f3 ' Aҫbs[n0ХoP+Y56W>&ڸ Nd#Nʓ0ODBll$/ -H)>(D`:K jju%:Ռgh!@㥛JMt=1 24Sv0,Xf[M)(d)\DWķ5a އC,(p@@v-}pThl@i TII'::R/<3?WbiC o:H9/Ra.ٔ W@G!JO}bz4YGe$(q$;3 MW'љ@?'/WtU0F#k:/7(fV7Ee +e*Yw}1kpv\G :ʐP(`2Cqi?v䭹uzz_f'" v.9>!g-N9aM$Hjjysnsn+qZHbTQ=@ϕgC֙RP쭥..˘v!ȎYAHJR0}S7qibv xOBYR!}А3$|svYNj"m :Fv'Mu\ l@d;|GO^lNHJy=K;*r~omWmkV[,^:8 [bznWQ{-A-N/)(NqIj)Enosk|mlƼ*'9M̢1n Dhi市<ԝ'Haݷ$B[mPbKeЙ4iV֣STKIyh `9ISR̤$$|Tqtpp"Y@Z\l%- Ĭp%$FGz6| m @* Z'.({<*őn>ԘBL| wWڏ!vqƣtmqO#2W ܖϘOЬKGphFipQ Iyٽ=WW&:r>|$@W@w/! Ռ=*]՗NwjHQ ''짖,&9 $|#W+ -Jpcp=:u`fםi`;Fy/:b\%K$Ds;SP{je'iO'*^>{Ĝ@8BJ)J7BMә`)A@>ҠϫiBx4w'֣,uD|oU*9` #iY$c42 IΉdIO\MAZJ$;AiA#v??I:vI!w gbi)ϏN8 t#\&)#)S6dndvgIid QR{se9'##obp۴ԏ\6Jx=?fP9ǎ<)\Ǖswg?$gh9ʂ@T)d-p<91 TjHRc㎡OMZHɮ00QHX?9-J*2d4q Bj[o#-)#A5,AxE)pi-C˶N&Bc)\CJFG4׾d%i2sX$2DGZm^ C! `KPR !&fjW% :] JuR`K.-Cf6"*X8 HRA9<8O]Єco\AQqN:MP2DԴnYRn--´-jqi nN''GX6ڑu)T5ԥZJ1z+Ih[s[!>{+q) DP3j%np>sJL) sN9SP"[Jg Tz9843|0*kMnNR_Ӷ8Aqvd/bJpܜmj9" j)urO]2u -coVC@m4oDjCeo%M)݌TƜ*|\.c)u\uoq%+Y HʈNj6:s`,*72Px?=r b%JOmwJrOv@IGx8VM)r.n u -`7Ҡ1m1ruLG~zr& ^;|㎔-ITƐ.jj#(ˎ$ԜԆJsp#o\6-m~DnHAPJImHN7( #ooQsaRJI K itecrmH) 2  3t?sUDf!!_˛6( A'#^+Kw*T=8;6w!=fb. ;6mI e\cVXn6oؤ!(m@R@$S'dϜš)O!̕I.1V*eͨ)a-Zu !iPRBw`ʜoRZʃ%.˂[qb,p)La=ݒOJUA\$\S/l! s+]Dtz6Ȏ8~!2_}liT1D0ލleo2Wa2%;K/I#wF|;T"kUnjqnZpUO9<&(Gu8GC=,N⠂ĦPy"1N>eM0bdkBG7$9߇ ׁꂝNp)[LzDͶdF-P!)OUਥ+H9yvadҁX;T~z]9n03rZQ9C 9k[+qeZYiL덲Q!@suɦWTMHbr.cE@qώ)14i\\6lBmQ˲!4 PlyȌd`QͻUZixV1g<%yD$nH!#n)(z䲻t][F*vd HBGu=)0Y,je$mRrNe )xGJ0nZv;-mLqԞ[};I** BD4-%'iVv69ǎ*ckb6slVӧ9K( 97xH\=|SrW &S@$tSx ۸|M8~9tXh6(9@:?w $?)ސV211XO_I4dB|uʔ RGRBLQ {NHCƕR"PA8lg=:t!jRF8ZM%hI'+NL ;Fs8rӵ;}p8?( x@Rye+koe8`#h)orRd6?M:7;2#zD$1j@ۻ@x[>@$pP<vQK@u$p␊W]589cy f-Cqw+ZMBW$3N@D,..1HlF@2>Uw8:)#]HgRz X=׺(pM ;b[+}`-4})=;{<֛h/a)n!pa)o\ ]i!8q:vFdTl=Т1(tڜrgƜ/0Ԇ)AB.cH;$hRYN8sBJۓUk^28?heG @,#88nߪ`PgNrdpk @+ya_dc+Q'm[qtNS;œm Xx$xSN.@%>Mb(GAN {3?>3$ɿ__?M0BAPG{&џfG=O־uR]kH8p]]}m>=| ?(w_ڻUKEUӿc?)FWvWjiȪwsO(ժÿO7b?X`&&I;$`p}'  gbj%^8'7G8#T/Ud bџ\Ә Q$G"҇.?[Yb&p#g~M-= 1␍*g;zRPnoL|OIk(lٷ# +]Ii5 ;/1LJǏLWDx= 'ZGk+H x/6A41rJm)=y5^AKA' A|  >CP8@ZPRpv$=)$zå9Fwquɲ.N):rF5rIa\%ΤMXZ\'1җ'I̸HJq*s >񮐀I;U]1.R.t9ܕ!Y]li)dyP;XKdQW&;F|$( +zW*VJQ'9+cnG7'K̺HIӅ*(}4 %;k< <.+vw O؟ɲs.):QۓǷiwsHF2\x֛X*9_Hs'\8?M6pۼ8'ږ ӃƓta|FxE_ F2 GiTBHƣG*p: ;YxMRX'hx&pH쬇l+9R 0Jǯ5\m2O|ÁҚ:qYwjr@HG5v y!ԏQ]˹MpR0Jv(`BxNV=P/689JFr9J9|V㞕. 9 ;j 0~=th>#2mu)%͉?e8( Dz߿E% <Ʋ%'|!Y'Θ0Elf >TjNNAMjG/'ǯ>=9ۃNe,ިJ8eU\A=9ץIQB0c]5q#P<RnwN |w?\0J{9ڄUX&CgIҞE̔ns`8p))@N)=FP||2)V=9$#" 8d'NrI'ǜ~dMoRN#<*V9]/u7rj N7q8La-ā!#`S0v )9Ӗ{l-$zR1.7hB|~J5 /OB3`̓^u2'4XFs8IOJIwP0ra˰IJIW (c҇|>#>xg/1B~^PT̺@#t5I91Sf:~ǃǭґaPRCNA=zt(> TGPؽ"Br3t!H[ϯ 蹼B词A9 7iNB@ۀH2J~uuw𧒳ިl)Ԝ'rzP/.gzpy&j]BN#pz jIpNcVNAQCƒ(ށ *5$yx"00j5np%uxo rU_4U^P;9'Cjj~&~D~RZ7v,%{U 315 2jb gbjI*<@M}U?pJT}Q:jM NX4Q+T!AkWᬪ;De!-)iCwy BJE!-$3R;djc褜 'rqAq[@9AI[Rӎp+Jpx R:sI$6Q a\U`/))OLQJB0y` +O+)t[ā@Zdlcה9κmJǍL %|֊l1H[J?Ppb3ZqE4o{ņ̞씺x=#liy+p\IT$4+g|ݡsrH8' AOK`$XX=rEq@BAIQ?Ŏ\)JʳBw६{gr򦷶R҆)guk(=9{&Bq@۝ݪhtQX)CSד[1*;/Ec ܃TFnA 'X'YZ0 \k=BTk%$jJ ԗʔfF܅I$u=\0 R(tR}::N7} .uD'[i a)Gv3~j)7$[t ^JI?7CR&vleۊ$YC8ܢ@5JEkK|{<' QHi6F4.P)@R3F:1ƩHʤ\J^[c*JsDnCђC.8‚E8 Qyu[=!SZ"b)!+BUGxΫ@JBr7uKaʙii.g;.+ڛs G1 8z}߲Q-x*ĩMFx?n*KW 3^-ˀ-+<ڴ4.nu}5jJҜ( }$obX 6Diq㋁#v֩>'CxSBx`’pG oV_2%w 4 OdGzN๒GUQ$m:izM dsNZ#9$u: @ ⣚d1ʑJ Ԭ$gwZmYC@ >>9\^O@9i kiUفۻzI9|J^}m>9-gܡjWM/W{O)FWvWiȪڇkOJ5jزx!V/ 1 =}5 355VCA98=pH?;zP*zySE>8A>-,{sOcr06+_YPV&шSiI^Sv>x1BոGN1MYYSII@H^tᅂw"ZxGU !8V8'{+@+8jllqP:2 d8yeK9J$2R2cuuC[~1 9jG_m0z,u7DHO슎A ʖ|MG fk"iicK%wrZo#]QzIˁpJV I*;k vi7MԺ!9!„'C>}i+hzKZFyĀ`O_1D0 K\'4%SHaίKQ51Rhe%X($Uzm(H #i'*=u8"`y;]wm:ғpy`\W}]*MlC*y\'EAύOnTC\)oXw5r與i)gcYRx81$ ̽A4<&:JGRZR]tBq!*;iRs;J}|dd NnFX2)=]oS7&VyFZOߊh4 _>% Z n6Nēӎ #ZinuG݅*FR8T~[a%$nI㌟=D cc>3kI}דT+5 >+Gb5n76I䥔vN1VDe>.Ml<@-Trj9&lOŻ[QӺirǛcfB@ޞ$gnÌh.i啥r'8)y& )%O0Np(N[~9*m8ڃǿjF4{A075yVF?ҍg> Ŝ 2e}ulV XbVleCM!~ivqtsSg6A#oZӺYI i=GA[G#yK&ejyxc s9/o#66x%ٽUAT=Zҭ1)շܠBGL+n|zU.HH)*$:bhlPjHI +1YLƟtn[SIPͧ\#Y֭:i,7K|B8JQX?p[VNa*Bٜܬַ{#V9>':1uϝyg48\S2pm'7v=1]H\q=+Б83Ic-J{ ¬;56}T 47f~nY!`g@FzNO5!--n؋L1c0qzwc#9e.,z\t$(-v%;pH[6ǹ![wbb|: \RauA}U'@N20's}Ո*D5QL.<n$+_PH89Otp\SN4'>8.ơ(55ȷxm+џm ,di@QQĕsjC?1umc-ETݒRr|>zK2iP S-RP%-#VݞfOwmplj'C[5i{{nlz,yնlrv 짳mR'T=%vbI@ˍy uNH5g[ |a6\C=@ &TXaY!E*P>x~Y#WZI:3znt8# [ÜTង1> E{)D[]RG(sS qI⏃qthpoAz' cmVT8 9_ 7&46C;vwPD%\Ae9'9XB)q䲑⤒?aG+'PO^REVfiܮZI_@m0e bHmŤ N>z֟sIHksYqZ#&KI2pEm'|0A.U)v$? ejv--/Hen3wSOgw7~% Ɔ/19"r6qfuLeJʜp4y_ rq϶G}y#N5$p}#iIگ<5XpRH8~,G  [h1{ix-DdqHxIw8)')ˆ#?Z3ےwsֱexH(Y2Q9Q3PRT*|p*. [=C QgMa@{nfvaXxUZ}GRVPBKP$Cp3c|W>HͷJخ 'oq<^r wi])X_؟_G&o2Sެ2[L8Xi-\{=P8 Aa#˜uY|rLlJQA'!$M@Ѻ.ϥ5!T:H'$x{}#V ดP<q1B}bs3#˻v rRZ:Rr|WNR6Яö:#.Jӂ_|@ϐ t/7W5wqӴΟG?EYQm6I\UɧP 'zTJTNrV0| ijpVJ=]'AS̵Vڻyzk[ZrVF|qάirr-ZJޮ]JKC4Z,!mw\4C\ِ =6~j{$nPw*aq)=%R@# x 9>6k)e/k{t)c/ڣ^1㨮:)!XuHZRdӶɺMwaBVԔPK{x}/a-.`ZQB"ZRGdC|bo\K)/pRZ:q֩m {jW2O#ψ.WuѺ#_ӭzZ#Tp8#!OBZQv Xx-mOAM5Q2~ >dgᣁ3zדvso#8ˠ802:u428%7CjqyRӫԽٮMlٮ&łD=8Cd-kmIBv@JO)%k&hijfoؗU4̌Cf h7Rs"\QgCsg[*m$>~ylgBybT0Ŭ9¶۸ G=#L[ F򂶌 8:m6ms{ υp@'6hy=H(cXB`yQBR_i_f&VÎeYQ?xp31Tt|^{؟!M2FG s)SNF7$*0>q1 iA*Ay8 Q8n*3R Y5]}a6ID:2% Xj_Ji GF֎j۲iRI19o2^•c(GKh+\60$n;}Th 4AeaZ0U' "B.i)ĥխ %9 `@Le.Rd:ʛ BrN6]ٴ-ZEsڗ-wpxSB4ηg%C;{z!iS.}U7Jr2:Tm]Zc4ӗXHc/- J; y8SArT屍J)$3PMi- @+,(HO3/2 I+#>Ӫ A_une=>)(q,:LCw(6)R[OWc|jG>mգd˯h0b426͐l{Uό2ʤK p4Oy[؁)9*mYFPsԪt>sommAu#I#Aj't !n%ܾa-m i<9 \ o@cj!R.}imKq`P*8q') Ih94=QK[c2sM&žv!Khm` 9^MѼ&n-JAJR/ q=ŞOܟAZ H'jnxn3.BRrR mIG$6KF(Sݶk4mpHюQv'ʐldru>]#Js85"P',W}CНcnkӨLQ!Ds~~LT qɨa(EBR8-` yG=CЌ֌>uwrh)BC;Ǵn*uRP\ TD!*?tF)HD h_ӕ:r|pJ0O2 㑚.Rbln(yR\)$60xOE̗i@SoK6@#{yC\hi_\x `-õhO3TmBMu<}3P31%35+q%) 9THLס-#bǖ}5 cm[^W  ٶ&mzs)[:pJ{~HcLwzf0hg'qf6Ojffܡd%oZ*>H-^)$0]gJqT'lk@,ٍc iKŵB-Ġ9]qZm͋qW8ڀi;IPOy&9<V \vГ;zs~R뺲Hݜ|)=@;\9X~u?v4co,!w%A\J}n`z*@m2KCO9|Ro/DZ?@8 o9&}!u> " u {V~_2_%K#"@XʺpRρ4pONpTvsǍ9`qϙMV|Fs19t =|j-+䃃ɩE#cR\dˊtEݓu$|񯭧Td߶r澶uIžp=گ_"j??O(jWM/W;Q)F^yGD=^C)PQph{ gjhuJQs㧷_pG^G}$l/**hQ _H(83ʅvFI}4ڽs?Z@WzdSj{y8$c)'+U c9Z3|V9wmtӅô]g+VAxqD;/=(( Ǘ"֝S3IRb .]-mkn;rUNUtު1~P\ o$P 5W]"Y[h GJ87Z}_uqzJXMw*?$nVќtn1q?W+[l6\ #p4>i'M/[ڕ"RsKkK@]\ltt$@=~sR=J^zJVBz08F> :@|vN;xy:KK!pd!Q`c|TH#Ô5ջz3]EhJJQzOk}C=w4nB==j*ٞ= lW<4͟ƙe%C8QB:DZBZjw9c`_E7d;7HOu@㮂IO(fv-3&O,ˡ[T0OLgƈ;7ccچt"$Q`$zZCmP a)-3p!hR<`dukIn.9AEͪI CmE ]m|p-;pPG\EdR;L&[v!6!;32éeAl_Y) :wx8M)nsi6P\);w 8O?ʒ<~N0G#,Ihx.Dݛ% ]pYv*\HZ!<'Jrͨ[XbșkK'-9#$O`;sav,*8tXu+O#8?etb3ޢ!YWzL1 xs)ao]H~lA0ݪׄvBVr$ PNZRG4 NXBBZqu!nPddtQ7M**- v(P>R:$!#HJBJ;~P|CQ5T Z{UH3nqv alu JjkT48i!TF3ƇcN'u3RzW{@p#%PTo '[xא[HY!kzdna%mZ,Wb79SkB9I,(4^͉g*WY ][t6+'c%oq1YcxpNqur G^ʰI$ H`(ԑ<ԤHJwj:-g&^ٕ(HVRBx#@*j\ogQGZP:T?إ{/Ib{D,櫂S6h NTCiLT۩sLu]7$a nk#X[^7g0,VSO~l$!82tN/c]3:*j)!*S)$wPO\Vjk>̛m#ΕϟΗ]>ӈr,HRRF"8}[5P@ d{iSzu4K!?␔4Rˆ;/'$M)kcn! $cPN_hږ!Biq!$Ɠ&rfXO`wu=l7"Ժu566*TD/lX>D:Қ[.FQ6k 2ٗإH|CuBC#RwsV|υDZr 9=R [XZ`h {]%k+':TquuV!e)2ԕB}Px֩IYqQ6-|Vq<L=eP9z}.җ,71[덡$#jm>ޗ򒳼m5l_ꥂ7V;EZGKmЖQBrN *4~"aVx =aBYQ<14B Sa+BBH `}ʱPđ*eLbyP$_c 7($q R5 pzzhҰ9>tzG |*7řOw;HzkA"/;RTB3;n>'ǕF=Qpx_.elĜ2R%ؽotm϶ۑ^mypT1x{L.Rf3-%,e@D uhmd q}PI_ ˆ;[% 0o 8g«'tl6-I%v5~žܘFw{=),%qBz}Vs{G3uUє[ XB,QrI!|pˡ~vpI˶葮2$D 4,lzܨQ) "%}vWޡ)ڕgwPU0,&މlqԤhqqsSͱDKKXi;] :NoQ[n SUIq kS-)piqQ?NܝkK$UMxUtK(Jm^ ÌQ mu'b5 %;u!GMS$eG#g$>Ti''#~Ji9ݥ;sM‹W  Ͼj;i4u uEe 3wY@It7q[^kS2#nwN)kV1uI*N,OhWjX`k7[--7xi'K `rfAI-ڳ(ۣP5lYv)wnHt)ky`d@ Gm.s*ؘz9:p8 BB@J94j[rLФgen+wZs&E5\r9Z4MSnںu TivG:zjԦTN@0jz,Rgɼ_ۆ\IuJIl Լgžֶ=HM)jjgDI>Ïs05m5Nm3u0ֶi?K}\쳘y&%oq@6TT0@9⏴+ƪVuMUݧBBVؖ\g!9WiKۚSn$p写h~qSv)vvtv5vm9d!ݩ<ո$yoRSNݯn.׵g(0׭G+n #c`sywog M6 qݧ xMTS{ק.2J<㤩gsL >[ d)%gÞ? X62nN#GY(Ve$g #bojpH,tydOnݤS g?IKR {p *ѺKȰye)w 3UgkW]TV l!ǶUuTs_rUUqHvFymDxd{Hf~/3oZs%Tsrԍ[ZTX ydR 89 e NoRAy8x1N>l͵mSwu_Q&/b҆ݤ7c\}Q/&i pYJwwN缍rGqFqoyzpK{NHJ u Qzꆸ?8fy IB[أ=-oDrvmە!GgYPIRFA_RzfeY\;9j[lFˉ*7$mWw~JN8kmj }ՙ.GX߂A V)+{Hz79Dv#.8Q[TWh sv(2\|0~V8lt 'N40G4d$dZ@CVMf(i?bS 8Ǐ4=b)A Ff{ApMW:[ |/eg#.|!0#'T=uS S\XAJM\-rvb3}rOF3+gE؜םl#lbFF>y䘣B\'*5ө\6m,n$ ;YzN#e@蔌khmƁIKeT)M:VgVs!N~+$e5:jf'|T#ϑDNAǹ'5Q7qzeZ"Pyilp>:<СA֨L8`mpRRI#衎Гw^6\HDnR]NxQؓ/r|byg'a$rGJb7 i$qKA=Z~f\Uo[!+ '=56ҶtI*}my9;Sn~cDk"$;ԎJ ;G#mP :֘DӑJ^(cghvm-X?[Ii򫛵 h/e)~&i#H#88^B[8J֢I'$9p9}di-*]ozEbRV ԡ >:fݬ1n-zHUkqvq3P>VΠ <^އ-mp8#֥.X_5q|V6d ¢Im+ _[Oa(8v}WUyOh'jgj~&}{D ?V <݋#~a MM9}C 354:('( ?=z\Z9&4 s綣3|$`;k,$ѐQ~o Wm}tr\1I0Ae8IE(0ddgAJw`dRHp=B9RAPJɓO^I smS!M h/nz=<>vh1“>֬C1*@$-ֶҤnyXH"4B6=ԓd/jO'ԮTt]c"T6f6JTH(RNGƉGZ2j pOzbVJ 0<'=t]Ӛ}naGʈ>ޕkb7UkM1mOޠqSs?j/m,tYeNlBVP}m2HY5x tlqtޜ)JMx1pΚ$:~F'ЛZu3Dɫ9%z0-!IY9ǂF3֣4RRV|Rr{s 2u@(zrЌClϺh2X@m!<@]ǃVXvRAb*tIaҭ<҈9iip=6YHQZV_P BCI JOs\ 1@UՂ3HZ7{JfԀn!K]]jJ \>`yI$-O"7C1cJ#C% !'H-Gשu#"G^jsF#kMLsTvאAV tۮ$W͚PDU*B=b}thd V@9 ' `JӴ4$i;O/npy_پ'F/LPzv|)O^Iy)= ai; }ǠR;‰iSx Bc<45*nŒ < qܵRN| m~U0RuCEhHsK! gn9`W #X),[IgZ+$r}ϰ˺eUc%G)TE.%c呂R9-hKAj%Sҕ!$N31TogG SmE8",r+*=Tq`d!u PHBd2Gº&wONT69黼CVC_~vw(8N=ܮZ޺ԭ%gpP1HTTaJ aH#+A KE4)6[JTHRBrTtmݘ&pf3K-8VPVҒ0Wնc-kPT $mٜٓ֩z]K]Q/=ʝ Gz!A>+r̉īqumFQ' #< N3Rr'kE'PZjVU3(|=52dpǓ>-Kpi6yd-GFueXsԤiƚsn-Fh]yq6ꔕc``B*$2ۓi(([OO-w2,珕ҟmjkKs Yq#|jvllNK Ay#*$zî?8V2?[a96O>3[WY.7gsG K <|8ԨC;O#O;x4$ yyRCF2K@0|9B雔WW1{^OW98挢z/51o t6ȨJ_m ZP9$t$5A8o)P^[YL$:K@ Nّ4EUL H `|$ݶc@w ;/u3NY•/O]zta8!8ܮGAgVM@PZskO7[3sqXHd6q=?BV"W=3X;nˀhFSR8'g*3%lSLS='%> KlҼpxeOIg1C>Xb9V۬G%2BT{wiQTAdzOZzuժK-[46Sq`t<ib&%J^rq~jYU$֝z;Ie%Յ%J>6ꌭ GҸf& [ChҚgh$Αc2,Uún[)ޟo)% )>xr5&2vSujKr W'5Um; X}Csc@+*8AI 9dkap*kJJgƝBcT(c8ψ? 97[Dkm;*Ht8HZ G4Znb!Yۃw.)6IJvTRO\`N1DDZ_wk R2qvYXCb8X֭?}2 6=Ķc8H;6 \]}%skxN=PS F C+_+}+ty04 25v> qxb2%HLj2)yT(,0B$yqRN/Kˋ.01) 'BTH"fVZO(+|U2NOT`B|9RHiO~*YO<֞1q+jG j^|_'Ԭ[oB9]PTTqǎ}J' WhPМdvKjm8T ,Qn"&p[<np(g$ NG.{ݓFNim u G[>ƙ~l 'eЄ[(q^IwnJ?M-u_d3&9|!nzJϬڈ|p|3I+RI)KspI9۝23S:T@D*eG)j!ߔOxsAyda#;TdB8]R*Od;R9_>$  )CJvӥ # h" $P'c>ynry$ Ցɨ+`` B(!`䂬{zWdNG"k.ZV\_;#uw(8c[q`PsȤZ I(p3ɤH9+80ѵj޾q'Kvi2ۂ7KiddR U$k$fSPV}!*Yy `өȠ┕%@`|Vۤ'Nd5dU\-O:6e2qթ!TF=&ޱqk$dZH :(˝7Dp3ρo\i ֹ߭IMEUR_}nH]tc(%0G@s֕VlJVZ1eH[Jsz[)sݿ'%X`qMR$4"9iMP7obEӨ[K )q3f4r`mqFC=ܮ]!6@ r |jJqImLlԑ9#4P5tH:Q#;)lvR턁cAD֙U)Z\٘mEg g INH=x,BGNj;>ܟk I}\CJP19!N!!AND'ԨaKO#)B=nAֱ ;Gy<ܫLcXlj +¦r QDw")sZ~b9THoll `!=8>iT; u]SqK%pP#o>>I@`d97JEՌ>O4¥:M,g"4D1n ƴ8Sͥ+WBX#|uYO[wI#1Wqy&В1rsު{N*Mqs%ZL`HtiYIJ^(Nԩ[jK|uN`<.zר ؙKef3eԃJB(vzpq]G  ^J):ֳzxIv<ڞJ9BRaIǶݯ'fڻY;ITIoW%l=JT\0<*D) n8o\$Gri9 eLD[tCj'1'_^z Pp^/$$Вg<%Ԝ~jMI٥X#K)}̈́8XNi+%Y'+R'ʸ ;cZ$G%yedg< ևQ8^uDZE̕ )vlL_HRZ=Q#ӹ7aN@LA/7qIF0Ag%# ) yM.mS#5ߋ'fpGǾ󩽞;̱7`q&;Ĩ9>9&w@>`EESnR(3=)!OUT$|6H=88 W%'<҉mkKƙ *RsƸI+FT OQm[N{%2 rđ2Tx/U܋) U G(8v}ʏWUySh'jiWKl5[Q.1$94x^_G3'IxoYK_?SB?0R:caEP?k_?SB!$%`>*VO$ 2N6,y`@ }M2\-LP74tVUc$<85$ȥ- >J[=@^P!JNyYxґQ݂Ud6o>+ QVxtn]qGŋxP'褥j)8ϳA?K )GF]u3Z^`$N^7(`}KrAYqM< Ld4Ukc*Ϩָsjьgt2BHrXGK$#I9hsS!H$H`X>w2 z{GZ"u)DyҒB I;RlN59ijlHJqOՅ\8\9CXSndJBA9?(GZ$g}4$[yV9!,z:RN6 PziIGr8'ADR~Q]ڥv<|zVC$8y'zIs[Gz {өnK99Hϲ8.Q`q]n889sI(9I9 { =ZpH9O}-ˍ}r9\up> 3ύ'rW&r $X (@ĔO} 0RyґVFѸ(ƒ8lX*BP1IW +r >=8&uW M <+o$c4 f1z 8NQF|z KۜvJPq#\[>\Mאi@3t==ؤR!@`+hQRAG=RN:(Hipz$ ybaĔ4V;͙ nRQ.,1RI܁p@$c8:wIi*SW#D[u?б?ubAOϲZI*86dgpI}h!~*($~tE=b:עWt1WÃ!80%]pg=9**Ci{q(|4c?NEP{D ?cU/^]7%"Կc=1YnőĿQj8354"#'>/[BD$0 =:V(??/z{sXZiR=iSĐpڱǎrgot2 p?FbY]@ "g*v8R~'P8 c3Eȷ iqEQpi_]9u{V~xaFD,Fmm3)=&T4сޟ4tiҰgSi04| \ I$$NxLI\NQ".'hi|J r.9<9>ڻ;$m`ATrE, "ؘv)dMTvۈ / uڞt|Y958*_*Cx 4k1GЫƒ~c1!IZ=.> HNJn7z<9Aīc8酐rIҙ@eP `!|y%ˉ=_1 ,~RH)p]C$'3;k2\Ǝ"V<מ)+5ݮ6p6Yg>^ToOUS"S+8 B|# 4lj׬2-J)%JWMĞx1;OARhkZH;aZgd%WʊǰR`% 't.$:GR:RT /vP1W)iiۇIG43ūJӐ7c5L.$)`ȑqJ {KOu?M:zmt99N>m!Fi* WJ1bYB|ZX6Fm5PoiJ8'8\}mޠ72%q ->jvD|a@|mr:b4RRű(N*^N`&ERPf.1.epɌcN.zǪ z{UO\^^i\VPB;ࣘP΍r)-=|H?\vK*K$V:OK0> V) Un mϢ>0Al~ T7KRcÃ\AI 6u3ĤxM86,)swNrN#=E#J܍H1)dkbhE3B u^C#pR laaJ,m4g7z-wVA'q,)T.sMUh 邉-8HWHm #jMIĞR:?F6'o9gδ[iksnь+QXKCj!VN)PVpzW'62QV額YRHFbPd>+kQ]HI~.ĞcJq98늕{ V %3)SyH|ø@ՌԍDvumssJNߐLSq%z"ue!QS "T咢GJI.Rzw V$1cAj1>Bzԝ>zB!iCAU1(p>GK.];<PzWK〢 '8aDgJ?i}K^n8.KCrOE Λk$hUȫ$E 5mߩf#E@nq)D3y]rn{p>e8ˉkAJzk7'9)}bvk-tkEe R8=SPDމ HI6hdx+S B9gh#zrp'5apqm19!EE$cu෌NV1BF98q@AIRJDug8F')IOI,sE^ y|c՟T3frO+:AN{y0I<}sbho8v}ʉ/y.Tgj??O(xo3ռ/6gOc=QWnőĿQj п&VNF NI<`~&x2p>~h ~??z`$u9Oovvg#ڟLR w•Yͧ?YeMV' NuUNϸQ)2S? QQ &+ :5ly["8%_Ahjb<.A;@Q88UUhDq"<_Q4+`}RhowgS7hp=as!klCyI nBJ֬eAY>)gy&lŬ|;po m DQYo1C2ZGOU*QaR/b4 '}>r ӟϻ#7BJSL Ag66V)d(3N=v Wc#8JTO|)YGGpx^=Pr`x:|DZ@i=q8o 2|O犇|l%ndjuYX~-dYcK̵:ҢUᓎ+,0sǾ{w+[<  [gfKMIC$) {|Kl\(qpn(ӷ{eՔǍ`KA+q{`Nhu3LJOv 烓ӟma\ g3`_+~}l<&sr-|LϩȓHǞbd1 )֞JR q57}m%NpKwQnhnʂx.W =%~Vwcpgzo7t:vLJ%dC>Ri0VF ma9WI%,+?)E8Y™[~g.o:fcrim8RcDvo\vSҐA>`iSwztMD\ wJ=mIJ>CⳔ zߖWSS/pNV+ouuo]C}m6ܕmVӴgxd .[QTiI_YQK.T~Hte+˘o &@kM qQ!i@88i+ 9JOw wlU$E2J$Dt+{|ƢNa=A"U,''<{*%D?W?#O­+MG^G=\[[2P$$njƽ!=Q1ߩ6#rJ7h+6ss^;2LVͩDJ7{\^tupXvP2$T&lYuV7ȑH RV08#ޏmhKkIP|Ud"smo#sQXvS)0.v-9"x~!9y=F[[tίzb-P$\-M$4SwL"X/LTF PPp<3K;Dpcz2%EQNDTU$cU /]\ج2fb$jG>ZJK={ :W XQ$ zniۄ%s 8@ݷvܒq4Zj9J$뤔]C 6}c/Dѳ--HUK_.xs[_Ԇ ρA@eԭͳ$;G[a¯Hƒ\sje=nBVxPĕ܊2 N# c˦[Ks4MB[)XaA>+a~(p9b Jɷ1&I i-!K qy4IDy+wY?!) } C9I 9GiZۣ:}CY*m $R}4 >P$(@y$ock~v+Vm6эuөm6jfJbm,k-!%F%}5?fl;-6}6,d%%ӒHqUK=(aWNI1Y;7Si-BOλE[ TxWP 5&mKGQ\ZRvΎZHZʹE:0 *p8-L_c[SC})}$7yuh ,vF+s'{[c*Rʕq0ؚes8}Iݕ01鑊,QVoW t0.En~oTFpVXRv񂑞{].cTsvR֗6Jk>&ҺA DʊxO!i†3Y;WS/rvҤ+d`ԓttp{ 42:[-o^{:}fϻQMbR9HOe9ՊƸ{Tz|R)5` Ъo`ZQ*BY9#ď)r#w;O| $p:8)2 |kdeXt5}_hE2Ie >$|G-:T|VIP8ֻؐn1INl.T &o+^Ɗ+oJ2Fqxކ/I^67g=Ža(,-gܪui{qŶ` =EQ}{D ?U'^]7'"c=QY~nőĿQj&<(@a9H/Aɩ\`χ8pH?=z88pZ&R}E 06+-IXǟIRU89 V89N /|>J#q9P|0)cBt'P )gGKA584`5;1> mmq}ͭ3(.(_n0]V7AW왪,xTېq*,1ScDgKaW9ꇋbGÐ6jJI1YދJ"L pռxsU&?S])U!֮9  O㭿2KKOvZ2T+INY;R\%X}1pӯ9^(Y}횙gYKJ98H>z;KHPFL^[d zٚ!|Aئ[.GyI ?sϾ_L~gijU!->選{h;xJ=Y'IĂ$+F=;?DTyi{q "Z(¶\ ?p#A#E&A$79mn'pa\gd绐rT=HҺTK (!n@NrqqQFǪZHdEiaohVL8|s~`UaC+y }u7նۦ{>LPqKv|i\qN@†?h!Md 0JKMʫI) jvTpi( Q#ǎ1ݿD9"H<`5ݦA) '>L9+{ONp%[t7 cšT0hX.'c'X;7D=ŻkN- 0BǢӱ`wCo,1REG$y>5Q~D1֚} nZcǖ8SSP{ق.:h$ų >SqTn2IzH-]vF}C$'OD5F_FN9N cytg{(h.@1g3I6l"2J-1HARn$e ܄SDzbT*3JrR !\x̗\IDvʦ`B[K|nWLc{5Bw׋&YԎۤŏ>1ǗJV#(z2{R Ky༒a~|V_Yk1]OKm{d7@F#0۲\jr{1Tvj:F!vuEb-<۠c)V2[[e4 BR`B𝵹@P½]>[J踖u>T'~E[s3DP+ RxuRBJ*lh3=˛s%{h*=iKܤҪ;;óQ` *-9!/JVTqqF3qM^@p=`G) Rq=qHmJ$>)3֒I7BRNG2R*8ⴤ:IJy##I$A_,{Gkw=punNcJpW÷ˉ WG0ϔ{}wóUu'^]7'"c=Q]i{>Գ2Sj1Hq9PEoj??O(լO7b_Xk_?SA2sX?/Aɩͣ  ] >~*ꛐTP }mG /a Y{)H QGZ [H= unRHt@W#S<0 ' s֐`1*LGN[P Y93蓱ڮD\r1_\v+#`U*z)B#n\Q\ڭp^aKPc<[J I']uZcE_t+l[SQQ`ꛮ bJ0Mldc`q,S9jKPDu Tjf5 P$nRcnrWV~Kk7DHņc[J\tg(=ݷ#<ϊָ{!N ~ Ik: 4А9<ҧF.|]^c)O8MmN7,nPb$wwHeXDvwRD"պiЮ1.]t|]pE< YMɶݡrߢ6!sn}ӊަےF{}]ȼJG}Au9+_,00VMjkVWZf%D%$KBJdsPlwixwwNN3;g=zWp7fXG1.&u6MXwFolRVrO P*4]C\új9ϐC>j. 6/ l)ԈcjG5=ae /m>Qz,W)Oat6|6_,ӗ"J[{ŧ}UԖ?ʷШ&_4}]Ï91rJF'LunanS5+S6R?Qc/a0'a*`1Bݥ4]?? {'9N%.d ;\%yZS=xSp~7?('L\C -N{RIǜSTdZ\4wG^FكbPy)БrƟjGsӃ2l N+5o{AءOi'OC8$($WS-uMHBԔ.mniAnSU^M,Ƿ!'rpmGk`)# _!%Cw5PJ!,*'ڹXd;!(QZhJGϓP:A"L Jy(XV#aqʷ (dFP>M5% ;nN( Fь#S}l@#LHB)dzS7m;J|nN~{nݢ  a#raIF |Ii{8)W#+>%HW@c4$4YDRnjmf#CqxoVVuawDsZF7*B9$G1㶔Gk*#'*4)?G)Ys*C[NwrI8#QV@?R3"7 AѾJHR~ulI,l/z򏛌'd R=[s~R4ޥUן g.]րE9݂8k&C}uJKHJy-n͆˼0D˄or|? #/@OIQ T%"Dn$+'<$ 163%ZI]CE7# B6mHqi(vKkq=В~qRJv[Rn>@wǚ#FeUxBB|Rٻ-$'~JA%īnBGmRI4{MܭQ=.؅wi]8$`G&$wsO%J |O>d$ 3KGFO'Szf2=b<e^8A襒0p:l8Nr9OddJ p|Sy|C6uyP-gZ]zG0ϔ{~gq;>vXjiȪڏg?F5zOUKEUNc=QWnőĿQj`0&šB MM 5%0}^0D{?OM\03ԏoJr8ʔ}! Xc>tqepc>Hh'p9t8 9|$@I:߫;riUxqJ#sSF1MO|f0uT ])  uݏ,D֭Giv}b#HWspH&٨i 8vy؝&wfɤKbT Xl@Ivc ҪVW-6.22Z[.'њ%Rj5+)6C(XDx2IvzJvm9ZB0%BoDD\w|ʯ&Ǿq&o#pH<{xVVJ]/h{ZjJK]6zLfKi•7=SAW{=Q}3MU=V%Qg<ˋvB[qF`!k~u,T pƔt4!IsI+S_Wv&޻\b+qmQRpT-8%Dq*ObckglR#@FN/j;ޠܞv,[gnLGRZB Z)W:Iˍ5Kq.ᨮm!maSy! d;Z-P/P[ѕpﺕ-@\vhcI*‰ڟXn/G:ga'V~tΩ XxLeAhnRd-Swzm(+O P7wSdmٸF3WLp,G' '&-Z\[۷&VnXx1[ LW \"l3\R~"op EklSI%_\Vp=خR5u& oF=iZ2qhY];eGvwASOϖAkZAZS[wXݞlmdʛЦ;)CpHx 5[]GU~y0fKJ6( RDLuj%-7vKR$'{l5f~FwPKO$MV_ciKz|&f^e8H ݫ'mv#Uo녨#ߧEn c-:$mFAd2㺬\JZ/ntsS]Ynv :SIvThPR>@>gH-yfg ߚ:[ͨen mBF||Q`&oLNxB>GZk'+թ\2(€ψW۾nZFd)2.aHؤ'=RzoL2QJA9U?OB,;dnKrvH9ln@~T`ԝp{뽎)k"ֻb/jX[RrhK!Jwq##aCW-a%T;DG4 zI--G$G}#*$30H\UQ˹졡q K ܣ6R C}'j kD;TĢJ $WqCw =r 'SBGXs̈q\by_IA+[/m𰴛lu{ǭڅVwgpHaP=h۟ SAIBÉe؄Y-% (e]6#=N(X'94>hDn4\x6v˼mRRRnzIɆ~=z^i[&:9fRFp5W˫k[ FLEҟpg&M fob%f)E.rRz'tv[@4k;=ۋSed2>j{۷cvkJmy2O^TNJ7eB8 4PuTհߋ Aa8]*YN)|H.m!Ѓm?j_J22&-oj\G7X/wS6JT9$ q <257F: 26$,-Y,Ls8RCKzzWO{5`h;mҶƄ+8@q$+aO?;5ـM#@B)Axj=^7du85Y<&rO$|" 0#N,t#]_Is8Qv:#Zh?Yy_IY gŶ2Or|jT WJ[Wn&cޯxd:种w۴\@bnѱQiInB[Z~SDr  H'^tַ(6qs3Q0ՓLvrT렟dх,.=ꆀ@9? 7vۛsUhkE@ zs۟I$GqvW-ZuŵR3Pі~uV1b{ռB$#yT'N*n6Na{Z^gdm$d >(jglVHE]ԕ:s.˒_Z8ܲF7N(8"Y/;2s$xv}kFq}An̦? h D%J#p8h%HeIrz&;!{)vAzCKTHϺ4—r8Dcr} cǿi $|߂0FvkI9Qi /k}m>cPm?ס}s GwóPj&ʝ~{D ?WT5W4U^UG3IxoYK u gjhQIíZYvNaǞqmrB@@m;@5h7aV}ñDPpvntf2'''$ڒj8X\y %ކ^>߱Ko𙠌$S"zQ88Oh 'n m ck₏7I=^Q=/oF :)@@FL8M =-t-^^~ت?=/orWek[tۄK--(#;qjMzPl[7a@dn#{T, WYJ[B3٣۷_\hl&@(*N9c+5k7ynLu%hDR6gmZKJq-gZ*K_P6aWxŵ18d)Tݡa$ =MMmՍ-ӸsJyh@Z+u, ;?Ny,'k MCԈ<~jε kRXSiVk%I^jedCj8)E)ɮxٍJi}@Uzf2;4,Xqhx>slvuwAsJ*Obgknꉯa8קCJۊ^H12\Yu*y}!Q*VR|dM`AݚvB6 q8KvCױG+Dx; -Ky\=s*ݣ#Kq۩y  t nKHE™vSD7 RqϏ;渏"#h;e~N zt*_WKշgLxz:2{oxEY- = q&ed7-NU6pi-o]y8#䓞S:yR{t ʓVBӐ cΩEaaÝϠ#]\;sv"Ɗ9U)3dMZW kk'E+aaakG7vTG$)jPN{Rx$=c>ӟy6.mV~YF[X4e]ȦlVԀifq$mzIBXbEh9E'> ZOmk7=`fbnE--ݾ:JGDc:_SwwZg6b%Dm{'C㢩 n*1Ja#G0rps[`yybnwKE*^$qu2x\.JAmU7n(#oxDݘHPBUO,''Bmb$9֫RSH*BG!t" seKۙJ5ַ 4 ]2Bg}-s?rw]C)# G Ke'jҔ2G8*o 6TToNY& R_ Y&%6G69`\|o2 'mnuDQ.&腾%I,6 +8Qԁǎ 7]ŽP/K)9 EXR~ru?QK[u}6h>W"{LɖB`l޵"' m)q㊈IAOp)eޔKڹQE-S‰zp:ݣALJiuSKxwyUoڤW(KAz tx*`wZq-Sό!YzS)Ciך4[jӝ | (Ǔu[Hr|"+al}JQS <u08 /R KȽՖfN}皩{wq.p)>2:{کC{M˽h,fp[( A9>GX,- ^rwwҨU&hY.A 8i+$6OX@?%56۸'6;(/tqG| `Z-IJ\V<8kDsl].GOm/mrYv\ǃe JJ)[$eܦ\0ݖ._ze((s90EJݞ@tQC6Cc\}lRQ?Mۮ*!+INᚭؙ)j}%>nR䠘5Z &l[y[Ukj=ɱ\ZIeNmpx0|ֵXaƚmpNyuLn2"%wqMjSڑ:c45 6@ۯPlZ'bw )[EY{UGTNݭ|B8KTeecj9e]76+BE@'ouO?9=?{X沲t\K>/gc'NO?9MɔccϫgcVS}GKz&^Zv{j~1k5=^Z=v{~1h5B=^Z=y~1gj~sYYK*:^.KUg?{O?9/蛒z'kO?9}W;=֩5Qr]/C}W;<֩5?w~ee.PxD.}V}~Tckꟽl~sYYKjz^.KxUUw>hO޶?9Kz'hMSk_U?jl~sYYKjz^.KxU?jl~sZv{STc\Sr]/C={STc_[VRK:'g[[֏jݞ?z沲jz^.KxUguOަ?9;<+~1ee.QxD.}V}U;<+~1k]Sk+)rOKz%TS5U.jL~sYYKjz^.KxUڗgOަ?9}T<-~1ee.RxD.}V}T{<-~1k}Sk+)rOKz%t}SkhGy{T5xD T?j%'a{>Y'1##^8t1ee88u1;N?9>j,~sYYSJ=' xUggK=8?zX沲^7%_Tn?εOކ?:}Q=:?z겲^.KxUT^εGކ?:{u>1ee7)t\K>_T^Gކ?:v{w~1ee?)t~KU'gzCVꟽ ~uYYK*^.KU>?Oކ?:}P<;?z겲*Ut\K>>]j ~uZ{y~1ee.TxD.}V}P{= !1QRA4Saq"23BbCr ?O6FyJ񺶮Ąuֲ$-P75vrUs`î2QڲkxL|O +d};4d};5̜y;o}asƽg?i'CPpr&>x359cMu8N31<&>xX1Dkǐ"9 )1v:L4Y!D8s8x}_Jƚ2q5dcxL|RHƽ;i'CPpG|&>x#k:/̜y B'yÙMu8N21<&>xQ(Mg]fN<̃L|O *M~Ԗ >n_J8mSe`k'hb+mQd1q&{M%d@(sǤV5dj1{v[-Fzߩ,ۖlNma@[A (dxkduJLFUg8Z{);U}/gA(1'd;cxL|R=y.{+2H"[$w$rی[fǛKou.6)$)3'CQpC<&>x"4;,%=dCvsi}Da[~]SAԗ9ǣ)y F/yÙx0./ƾKi'CQpD|&>x-./zkǐbw9O )1J|~5uϤWMu8^2-1<&>x9uϤW^\E~4Y!E8s#>xߤ_^qU4Y!E8s#x_|e]fN<xL|O +OBpeHx#)n_ )'CPpp<&>xn[C JW0GxS"BK(y{%G Mu8N31<&>x}-3s_hkkkAyA2^X 5 b^PAAAU05Ub^PЪ^P«Ъ_Atp*W UZuɃ`* }]}kwHY4KZdŷtHȴΜB,* J3$r{[5iF[]]8/^0 Py7'<=]!;Ez#DVEß-*MT:FQgΘ[aFZRGݫAخk9qQkyjsOPlzIghjCo%$aMP@IE~.I4\/]gj/}0%\/e|Fz.]T0e"ky ̝%KY C~[$D`rtWoHƿΑikX[/NpV^)lj9Ȩw[ӭmbˆ%+N$@"Y h-b-[g}ª$f* ]yU^V-]J]xWX2d*U^P 䪾 ª쪾I*>^_ª* kI> k^^^fߦ|Җv+̅(}QHgG%Ojyc8dƄ>IK;B솚ʙ} ++dK]]š$ҞQlcN( ?!S~n rF2qjަ?[d!n Zwr#'9gn+OteFi]PȠ!6[eO7HWKn)NHs. :<= F0nܗBBUސiC/ (: ukZIi҂~V|הu-dDJ'5i\&sEFbRJLpRCg?) < EH1e)ABB9 $tgd iyKe6Uj[:jmlWVʜakmrR{;q̹3v&UoxvNԤˉQZk>^zjGauҸSqpTE!C'jxx3%&HKD娿_+Wl<;U6sf`ۓ3<ʘJ8!$,_{f^C!˽[cRҀ˲+FsȪæ }:XŻ !D,`pA%;c:KV[S2˳‡"1{=;@[9sX^zۋrvU')da8[RմaG򮛚#IË&bR<y!XV|sʡ.UtsVtDUY¸hV6.g8mKv&! œIln8`N􉣢-[r%炟@8 1B&vD3nm.>$e9 ZZ #XnA*ۢٲɖꈷvT쎰i-)ܴi(Q![AkcGi-Mj>RTJ\B*M =.q@MjM? 7YoZF39=G*V"ͱWjg>H)NrAHHܴv/?[e36$I^P)( ʁKSf3u5!/KSFvm %N- fJ7!-P*nwPZ"cحٟyoDJ VCv]jmv-^ aXy=[-Pv`sQ]i. W·Y/eꋉ1{dY8OZ:J z] rWXs\yJR`c,Kml*BTP-$N"Yb-ҔcO1X Z{C1骞n17ңܓ:Dn+p!-rrMI:8\Wt} J.=NZ:`ޥǁN AQ Y"H0tk.eK 7'{iIH$s'QC1y E Z['g<_gR˳cu&\䈬HBl$ssAhw#K2Irɷf"UiԩAS$vFyrqt΋čn\zyvSSŸۈv#M8S̕dzjTH.#icITHIN(',}Pp,׷-J1`2'̲[m>h&#AST 7˸ͲRRTT@6}kg[}{M ZM^o67}k>}`Mrn1UxUXwSuuxUXW ۫Շu U UUc^fɺ&37 5#@}LfyȦh5Hכ5kY+43LҼ3@k\oV W6[5ҵ t*"1]r9(RJJ^~rWRkF)_O?e6Cz6;ܤ˽ikBKHH*A8'[ָ-Zj#Y N2I?y5fS_@ϵ?}>Z˚fžW3Oψ3)JZ7\f(ĠsQ2} SiW3O嬼#053C-򾁟j-7}4|gڟMj-e3@cJ}K}4dwYOh;:[?&+n8T8 Z5j ]gl62I8JR$i>xj-8j-}q9+D%\V9aKh=9[I_@ϵ?Wt_3O_3寭@G%'2[m1"CS&I$IF#1"A;Km4$`% 'V}@|%>Z/׻_3r\ȏD$Mӫ܇A YSuEԣ/׻jfCZ q*œ078k7_3O [V54euuIJ RR0yN['4e,e[Sʂ$`m9P'Y@϶?i@϶?%:t %-3ӱ)ج%qqy!ʩ6Ȱ0ie*ĕ8\uH' +rݜP4gN4gڟQꈳ+#R%*mP‡zAG.|;;]"\l{B-(\x \|- )4'݀'i@ϵ?K}O\r\ޘrLJCZ49` CxFǹp j$% R׳*j9NVN{*8/Ӎ/׻/Ӎ/׻/л/׻Y@ϵ?Y@϶?MY@϶?},gڟ^qe>ZM_3O8j-}7PZq%>Z/Ӊ/^dI@׵?I@ϵ?k^{e)%O6P`zj'_3O_@ϵ?fs{]Brl[Wbe Ҕx8@N}/sk-bۜt-Dˋy;#'/a^!=!e2wrNR{ @Y}>Zo SjlC63!DD 姘R dD#:B }SZX7*{/>{Cpm'(aTT6+8lX䞆Z=eGRVZq(?zUX8%}>Zo Sj!-Y:M`sa.6hS(@#(G VF}RD XԥHWd>)҄ m+"I_@ϵ?}>ZҒɹ3gL,&۲X.PR2$9" BbdunegT2?h`jCoVz:P ehZ\w wڮdwrQ%dG+ݪ;qo% -)ys辶ٍleki](PB9%=a=5H̆͊l%Dޖ6h^,E/m9^h᪝N__f~}od|);'vG¦SWSWc x=ݑSWSWc x=ݑSWSWc x=ݑSWSWc x=ݑSWSWc x=ݑSWSWc x=ݑSWSWc x=ݑTWTWc x=ݑTWTWc x=ݑTWTWc x=ݑUWUWc x=ݑ~y˴}4L $$(7'i |Y"r*.Ȯ qajZTTTsǷ>{#I ޫ/3N/3R/>Ƿ>{#I ީ/3N/3R/>Ƿ>{#I ީ/3N/3R/>Ƿ>{#I Ω/3N/3R?>Ƿ>{#I Χ/3N/3R?>Ƿ>{#I Χ/3N/3R?>Ƿ>{#I Χ/3N/3R?>Ƿ>{#I Χ/3X[_})KdJҼ$-9 wywTvO쏅<dRBds9ZqJԭzp #gcbg9H x=ݑc HLS-ECܻ,Ort' nT;GSEW{n,S盩PVNԌm۴';dx=ݑc HL7Z߁rŰx72q)Ka8Ȭl ,K5ޥjdŶϤ- ( ZAIIyY}od|);'vG’+"'P .sHoan%MTH,dw҆eibҢ*#'iV7yG׀c x=ݑ}H+{~(WUm(`%*RRh[I$qA./ǑRJ4@gޚ#Z7 d)l22zԡaGN9)cXcб>ͱ1T%2[!@)I 9' P-": ASKKV$]*/m;:7Rr 6j qJ#Z"8_ s)V gάGk;<\lյi#Uȧ\c5sAʝ}I)*o%=8$y>®KEj65IzIW "'2lp:6aX }:Z- DnC \py0!ap]$K)W g,aX@vll mb"m-DS i":HDY*-m:8] (ֲ$e4z}*T/sW{Uܫ3\S1. %% =Y0FU|9ꮥ1:j j+QՒZl~sը܂ۖDv[2l +ܬ%@CrѪBS/IiNRn-)Z*Sish@PW!@J)Tv@KҳYWJx:-MΜPJ ޯXeḃׁ}]Un/ kݵ VTIH ΕT^*\ѫ8/%NGCNJ\P ܔrQ&4.2&\dLۄd l2 F2J~/Vnt7V·ɟSţl+RH  R2=jmH^uu}X݈Z;dCDqJ_PN1lҪm/uBO;1}jKMbRJPudJAH#Wb:\ Y Y<•=R-62r Lg EeM$7qaA4ک|i ~wH >bN {Y}1],iozi )JXw%;|? ޺j ;v+$=-jt 6oK ',(8蓈,oEv=:^V6%툋cjWNu `E-,@Z =*ݕvDi2mN,8B A(B{vq+8cK~[j̹2K-)*R@ `NH! Vbe! 2aBGpJG =BR{@7-PA~DtJ^]g\5&X-Ra # $m # 1Uեρb[L =#nqBPR@ܠ HGIď2<]1da-4O`' #Ҁ6I!XCKͅ!`%@{^aFr[MSI.! RBwElRI6fa,CF2RF2v9B0Ow}rGS7d#٧&[v$kRcBCMn(BX*XTrO5V mV %cMZR)8!)H殌q-څ+"2a@@}՞ݥ4껵MLAm''?}s\жFeP[ FrCuIH9 'T8tӬ6VyB^7 i;Sy^5tLő .9&A*dylAWkovyn*Ӱ -QRөy6RPJ % HX{Ymp1q1}ti@i7h4f۶BB"<.pR1Q/$s;5JP R)@)JP R)@)JP R)@)JP R)@)JP R)@p4cwzF&^/|I%{I$`dBf3\j>R]+n[u1PW~PT I;;Nif.6W(z#kysVFQ(MLiUOZftYq8wea vIwgN/"ްBxw@s.wO݄Qu['g1zD9<ށ^]tMbuk*>8/Ɉێ7J|WR{OkݟUMX4f!{]1n='T i?墦 U΅<~Ծ+qJz:_M1i,C ڕI'jx5 wt ˏueN;H)=O bi>f=n=\irI㮡[!;FAHd|M"|p˴Bl+2Z~GUҩixW(O+0/ Zsαw,Έrsn!Sc*Z)V98Xߵ-rb_N\3Diġ<8un`! sT O~9]qKCm!wWSu HLxRPH 'g''S] ϖٍ3m.m[ Nb#ʎYҿJZu߫rBc5Ll8 -|o1t~h1:ۑnDVKkP zR;Eˬӱ؉YL-ַJʂ; Vo͸Å#ǖnNH;VȜgJRR[}%1R  JT@I uk^eƜ)mrZDw 8yzRh7!{dƒWN'kipAh)JJub#w-Õ(% % D@$YBeLq-IBBvVFF Jҳ]`^!e;e+bqM8(hPqFF n Tvl6[߁eJf \–%*QaHXA]L.nSm: Jdr;n+Ow }ƀڥ+mDKJxF\,/:V[%D%=( MX/j't{J&{X=lvIm$6.8J'i *EYv:^\0n5 r0/!Hx4RqocۛxIi ݠ(Z#H,m2KeYTC= K9G0"nK8^ꔆKZԄ$%*VBv0%T()JJRR()JJRR()JJRR()JJRR( %8SߑV@<1, 8-pwi"kwa*ՏPŻÅ%7h:vk-RӴ8d#';P@I&O͵,w[ko$5e,ҭ!=s5YF'Lҍ[ żo gm)R;oy @U=kkUsĎűFm R5q/27V)6%-mj3Bzܵ9d JZo mq#nRrpjإD[l'Rwv Jm)!Ǜd'`ߜ-4 Plk;r-6?/{T>k( ߥ?!u~wq=YMX5gG9j7^Hۉhů4-8vt)1]l&M'G?.2dLy 26/$'~q{qF|oUSQy.>(*RނGu1$ؽD A9W,5e~ʆHWQJSE:,縤8H2EMn.i{FftݺL>i Hi)] wNH4t#lU[UU?r648H*m( f;umncIG.$mۀJ󍼾mmVÔȆ v6oRA.9ݭݕFJpuUt&#4Vj)jMUKb2ADl;f٥)VR()JJRR()JJRR()JJRR()JJRR()JJRR()JJRR()JJRR()JJRR+ΚWly5btC*cɨy#n$:O'Ɵ墧 TIT Чڗv5r &TV鏅, N=BnPo&Z ~Z[e-s[s?5fl4pCrb&@fX;PwkFqϟ,z3H\uExqv1ܟ:RWFiI dTzj9f7KA%)`Q8>IWY+nBC8( NU#U}.jZraj5 XBTHD2.bynIvr=qo,ͳŲ4m@ 8=$-uC s#4j%!Gr *=I[Fe{BV+RXuzt2Kb%Dg..M~Ti+qT # fLWLi5ȶKjN֕"3RȟTsۿG47-̾n['vݛ=ykd{ђVS)nkiwFyHrU/rEz$F6%|D( wDEBt3/>spXJBtlNFvIH&s6]rDG$\V=Hl/< @ ݚk(wڴ3i8ҡ\x2@(ڮcE~G7~m+°'ƱŔَ p,gk@|y^N֚m{b"TbSIRmnyrD-i77i=^ ͼB YqRBFn\Y0RgC7qio];a &TŞq#v[tfj2܍Ao1d6llk VxkB,,S LU238iJTJ‚A <{Hisdˋop|s @;"aֺQrf g\yLymJ%Jv$OrV]-@)J#BAA,iTgd˞fЄn8Je)I%KHy^˓Rx֋TeTsHP;;H8~>`vvmn6g94ۍġe ) <|5u&.-AϰƷGCJT%)ex䑜gRln3f;(snsp`IzT+T[2q!!V+J II)m<M~`o1ɏa AY#TB)@)JP R)@)JP R)@)JP R)@)JP R)@)JP R)@)JP Rȝ K$57-q.fTT%-%* 'IHBtYs ؛s*CN7wmMo IZN䐥v 9lIvtї̧q[aMlHJXY;#$'̵B4N~ 5V̺>gsᲔ(KR{"IC] Kv>4B!R)nZ{ui^KXanN RS <@堒㞾yR4m1j.f˛KhqJ#v'vH-uVqtJVۖqγ+Ho2ܸꛩ\-:.Czҫf~gpo\[]q[Kʒ{PvmuFak뭚5T-FSL8TH“A:=1<ԛ<) -Yޅ:KjcɩKb` j]`MFq 0-9OG墧6 U΅<~Ծ+q)~zn1S>EOƲ?Zu,>jhݱBGYA<f(|xɱKu1ZGP )J##'Xt3,e=5İ\B*'7ˎ\{W:7%2j_:+OMOȆAɹuyU}TGl~^1EEPP(qD8y9»CBlLZrBu-t8%L;p3D _пЩwR?]7<-V;RA+V;Vd%K}UGt`ɒNGav{ (2y#OM~*:=iJDBc՞:0Գ@:L&\ #~ԯI"+uO d^ͩ*8 suT萒-XIԜggm3F֬ZV})J)@)JP R)@)JP R)@)JP R)@)JP R)@)JP R)@)JP R)@)JP R)@)JP R]o&.?p)s^n'S_* wYZ*uk[@SKbw޷4R]Ov5-?-m[kF3H>cVȉ ]"r)!WW=G ¦+bS6Ho먿Nв:T _d9{W1͊»yRRFRqD =ԶݩMUQ4'q(愔`a@*EiqtYNr9z:Db>٤| TBZmK6{O"BRNr@0yr̿qxhFkzAJ0+$SKAiR,Ke6RF3N8NR[q :ϤA/E.6t+6 S%$q9%XR˸q+#f_"=Ns&ےRDOYZ}_=Er]uM-K)K'r թXK7XIj.̩>ԇ6@ A $UÖ+_FPr&֥SZR_NR88* =e-DqW!RJ\RNO!"=+)XOM"*Qe)%Xx](5R#Z֢"-fu*/\E<ܲL=La[TW" kTc|Lx+:/#(;p2359ʥ@d5ZX}2z~ڝGDLmDS  M| ZR}gт ؅[ݾܰu1BPxNcq))ȑ)ZE:n T>u>kdKR{"qȊ0]q S() (CՐ pWսw̫#Hd>G*-G\>{՜RVW.d1-WK*>^gˎC%%H%)v( ǐn?Gmry[Q̒KeO^չ`+a8eJiP׿ "\r!нikXY8Y.+'8+[ShVq~kYq)-%.)8CZ0 G*ROiŷFYSg.8 HR ˪@GVvvy$ܚ9qu1᱀P F6hH3JPt I밮wHϓ9)juڐPvNRr+j~^ .M<nTT%jZ))ʂybݸiŮ}.pWm40#+-nv~KrB"MH0tfBIe9idyyk-`$g+ )䅶JXHHRJR2@VzJRR()JJRR()JJRR()JJRR()JJRR()JJRR(#ƚL5|!%{hi9<kiv&]C7IqS<=I$&l&onۛNDT֟Ap rzAŗh4Ĝݭ>9+ތ9a?&^!!խJ+.Ne-@ILQccBS$LPu+9ڜgd~,WI{8-<7)$d+ipѺv\KZRRsySۢM#Mol)2"uH j.#j"Bk.75DmEln]9! lH Kx:.zcM]-mڮZz6ӥȄۍ!û+P +xhMk?vy.mh Nw 'hjkTmA:zH[D6K˪J <@+jՠߵ7ω5SL4(8)*m*+Py wO݄Qu['g1zD9<ށ^,+-e˝i/Bg{ #)ϑNJ2 9t2gcɫʯUMFq}?EO~@?EO~@h x} WnY:ri' or3k۸o~7]B?Bt)A+I$)==)4p{EJڞpκn [q<9= -ǃLMp\.% w~Wy Zt-rPֽEqTMNRoZHJDP>R$E͗,.@X5JI?n/J]ԘIa[uhVH<'/|ݤhXqs;Xa O岢C'G][ʕ0WvJ[ID5v T[[,LFvRÏ-T J$rWơ,I`bx#ڤ1׷g3U"t7 ս4ӧzEF%冖YiOTfktFX!Ǿ2ШMۭreqӍ<R$i]&ҺթfyVb·E+JU@k`]`qJ-lWE[wg1KrV6[CV &mJxnB\’ >sǹiFն+&-,4K&uv0F6S**)<( %\wˀ2[ء G20r'[B?nmZ4L-Pa !㍇Zr97DS+݆bQ ;1G*K[ n;I 'MGe6(QpSzޢ9Ҩ^&!]JcnsscA_K֩ɵEur%.+o-)8:9n vծ{מRN6~:aJFRs46[PCV&Vܵ@Q䑒H_UpZeݴ𺵉lEP 8ls@)R$4cw>Xn jin߮k"1Ls !eL@R< sӶdL"RخM-JJUPwuR:_F]_R]EKȜ#6IZT97fV{+ oo&4>mjShS'*~8Nܥ%`%p-ZL4UlR[Zxa2m>ncg"]6r[ٙna7!UkV{u+eIE9"'iB]< ryB[^1S)*€# w_%twl$n<&-h)(0iAin'*WlКL5-q.\e!-%N158Д)L(8 F`[K չ cx[{ R0%%CVS]ﶽAKluVk=i8s<%Ӏ@#}@)JP R)@)JP R)@)JP R)@)JP R)@qڞqStr.P[Ii Q nr)#kX]|m- Kh3!(PJ.)!2PUU6]pj~e&W% HuJR͸P;璍Du#CFmznHnN.% B\Fۅ;]Jq.e@%3m<6T'KL PxMBIH$sˮv˫vw :mICAG>jGM[qrc-r -ͭ!}Twkv,jW7JӦo %俌$Fh B4[Ze7c:SBPJCNךY$}8 G'A(|TNMb\l޻_/΢:Di,6󍴅9HQPմ5O_,:$-Gq(r$KyQaͫ-,e(ڞ;5 .}ь HI%Ψ4 dM}]ޔ>3.0%cM2Bu$+ YH8ڔ>?p+'NŵVΕK#h$g8{ΰun? -HU (IzV1mILF Ϻo^ ͙/HCd0nt<4CNV*J<<<܈#TG˪hmsR U$eɂ[݃;F\'cttݭe^P:RWʳb"6Rվ)J9JRW;P-Htan6۩y6qܴ8 i 箍( b&k+RԵEJZTI*Q$[)@)JP R)@sV[mPɕ!JDZAq t)JJRR()JJRR()JJRR()JJRR()JJRR()JJRR()JH?r@jӇ>51n'cߟ _*ֿj*`vt)C۸S%̏1o4ݻGSnT}!7vC20 cqqO2pG/Z/kr%-T֐#'$EMObꭸT*E|5 0I\:CmDة/E;9IXJղ!Ny]954Zi+aC# G=ci_҄$mU-!tUwCGki봱 v Hk%%dsE7,-P2NfR0:IH{DȋVB܎}_ 0AT.qKLH1Jy RR |üڥFfl[kP^ l.Gl-\ڢZ0Uȑ][ 6+^u|iQCkeA S%vn*Tc/[\|J%;ԃ#Nr@E}']w]3.X2!%9l($AH *>( -*-'N-=F&Cly*)t4oҒ rt:L[*P32Ҟ 4y)-U zE"sua7} u'jl)k8J<=fHzV,GB%Te4pQ%\) *.ԚaQawr;K)ZI;3jCYZ\<VSҒ22R} P TUҌQ>So&SpC-p6w68[ӴhVF3[qu{v'R!F3@%m%eD)JJRR()JJRR()JJRR()JJRR(*33TJ KcMap[Ji(QJ[$a`}ךWTȼj eW*B8BGn'g 'WA&5L= -M\9`RRR7uٕ5v4ؤwfRg:QIh7G'n 廞R[ʵ5njpJ[ p;)Cac]iX])ei-%JBSHdr>~YBlz-n]n䵺KKZTGqV|U<[ZHkZnݾga3%XM͗&2E"EbqAa/ Jw y$y"[i6BTOϴzk^)yU Y%)'eRV}uZ a66y_UksǸz:`6uehxy݊5ttz\*^P/sbm/?E]tM鐞ڜ$$)h)Ep"˱HPWlgs+<+>^TهX%Q۽K%L˚dx'v A2;֋}n!ŕ&6a)ܣ( UX~ʶݭkSƙp˩^(07$@QΝsJoՓkύ}nA/3eLWc79R+WY1P!-%)$8Hڀ΁qnums%^qkw-/!d-9 J9fҀ4G7~ڟnStD >!Ŕ 5% Q%0qG)<֗X!xJ̑ PKYLa ʣ q* oҀ-7Oޭb٥-%!+ii% )#~Bc.&xZ{" y7 4RR $$eGnNNp,:PސR $(N0ی`MQyD%4Y{UVʓT(Oj5+sB )ZI3Ձe)Vǖ4TR Q*PWRMBҤ*)oVp3?qRVz=2k??Ze݉K X@R9sni*p:Z*J(99S7G!m1Vْ8ҥ#0 oд֜j)D! A)9I^FFG_?fޞmp1z-* RT) ys#<*lY8Էf6khX )GN}1V5$MDWf'JM"#,M55s4E\dDJx}}Q'Ӊ5jPr&PwOo/)umeFN'R7Gλ'MݗR|OYiAț#]Jx}}Q'Ӊ4ԠMIumeuI:$7_}6v_SJ=fOY"nPwOo/)umeFN'R7Gλ'MݗR|OYiAț#]Jx}}Q'Ӊ4ԠMIumeuI:$7_}6v_SJ=fOY"nPwOo/)umeFN'R7Gλ'MݗR|OYiAț#]Jx}}Q'Ӊ4ԠMIumeuI:$7_}6v_SJ=fOY"nPwOo/)umeFN'R7Gλ'MݗR|OYiAț#]Jx}}Q'Ӊ4ԠMIumeuI:$7_}6v_SJ=fOY"nPwOo/)umeFN'R7Gλ.oVRi#7-BTxIOaJ{%|)X, jn抳^XֹT5$Yq_ǽfUB%k[q2,E)!#> ~@Km? ~@D·7j_$1]R48]q1WM۸FR/85ez$fC*+dF~Jbnn? .ޮ`ݚ vh[q%]X@rH9I ZaE).*U1!{V9yjkr+\H̰U2y%Rcs1[D|4v5[gVSr[wy%Ġjُ2Cp-Lx)(1I^s.w&D8Ì!҆JWa8#v_ԝChHϝl\-vVI%o} yQPʼљѯk}ʋ+%/q-c})9tw)_§0!tjB!Is/:,*RWvܓUѪXo_*R:N^+S#H}z Uf6SUL~+RGmKrqjs]"5G+*5RSk dcrtj?yvrhw+oy ƲNLvxc}u6WV( cAnpzd#p'7V}XPm;RD㿞9ּ[mi t8m8V2g,z:+U)o9p$a*ZRTp;ϠVօ3qc .1ca=#όK )+ .(-*);y`k4 &KZ"Da jAmjm\† _x8R##Nibz|1$ Uu 1:iy[OypvJxL;J˅%I>=}1{&ڎqT@AB/OXltQ-/=uU #A1tꔠRմ#?չDLn]h)98†{>Ow &$TD ݻzk˪!)r,`P !@;1ϼsyj|R D)$wvGƲ5$6VcE[RIN7c#ֺB6} W:ŲndPkmKmt.EyjSYJ dz+$h\iԥ]O`-A! yy|paNr#uT;Yߊm-N(n9Z6?~5xX-f&TkӇY6m55j/%Lݐǒq e5xe/+,̀}cθkKgHjʭ[/f&DG%-?9Kys8ne`#מa M \Io[a@_=W-W`YmjRPRwgsGRҙKI@݆TT;I<ɬhcgOFI?8~v^<۩lBqkR{@\c{/KE dUޜTK"B^Kp%YZ>x.J8sʷ4G< Dq"tʷ {@$pA<Zꁇ? 2Ґv!,K p!yu ;q]95j rG ,ߎ>DIYQveI6יc~3.ju*ggN2Rh^{X$ynyIؠ3hVԓ(rMr"/ k+wZS|YB\-,d*$r'*o֜_ħ$<$WZ[KRBA9 s8HgJniŭ.-8јқZqkKN-4cJniŭ.-8э)ŧF4ZӋMқZqkKN-4cJniŭ.-8э)ŧF4ZӋMқZqkKN-4cJJ.L"չJڕFqdYXn37nEoyN1ϕrܽ>hqm-I0'\JM]ci*Ki0Vwnzq]8鷮dCHu庠"yy G0 VRZKnvF{]øy\` pbsJˀ P!؎1j%Դ)X$ +bSp#7&Rc l( RTW98;Z¶o)E*|_-݃eԦNw0we9³vy?X'Ra"l)꿃96(BP؞y9wWXpbCĂ7+ՃW7n*C䅸 q'P`X9Qp_7msLQV||?UfYr=j9%Yҕ!/ *c_k: JEtIp 4Ip gCXݩ|v5:7ԉF1k_nƹm=tkBcx )V0W^QptD5)j"aə"lK1պ @V"=j ~S']Gs2:+TwNo'lp+tUcSs][` Ϙ~΋֖V28OՐY`GFWo??= gII?2<?*&ޞ^  ,W ّ3uvXQ1mQ1] y=U1ӗm +?MRU[r q##9G2LcVURv.(:(1 dVK$%Ube4'i^iūؖuGž/~Ķ{>E>9hnRӋW׋-ꏅ<_lT|)(9uZ?xxKgO?b[= uEV&(^-8}xaSؖuGbGUJN-_^/~Ķ{>~%QXQhnRӋW׋-ꏅ<_lT|)(9uZ?xxKgO?b[= uEV&(^-8}xaSؖuGbGUJN-_^/~Ķ{>~%QXQhnRӋW׋-ꏅ<_lT|)(9uZ?xxKgO?b[= uEV&(^-8}xaSؖuGbGUJN-_^/~Ķ{>~%QXQhnRӋW׋-ꏅ<_lT|)(9uZ?xxKgO?b[= uEV&(^-8}xaSؖuGbGUJN-_^/~Ķ{>~%QXQhnRӋW׋-ꏅ<_lT|)(9uZ?xxKgO?b[= uEV&(^-8}xaSؖuGbGUJN-_^/~Ķ{>~%QXQhnRӋW׋-ꏅ<_lT|)(9uZ?xxKgO?b[= uEV&(^-8}xaSؖuGbGUJN-_^/~Ķ{>~%QXQhnRӋW׋-ꏅ<_lT|)(9uZ?xxKgO?b[= uEV&(^-8}xaSؖuGbGUJN-_^/~Ķ{>~%QXQhnRӋW׋-ꏅ<_lT|)(9uZ?xxKgO?b[= uEV&(^-8}xaSؖuGbGUJN-_^/~Ķ{>~%QXQhnRӋW׋-ꏅ<_lT|)(9uZ?x Uzq@"~5gKWWֿMYC,/a%yTU)7(Ns4JRi)#"(͂9O_nŤUkW:)쥾e-nRI? @ycC۸#+a!YcjnQ<]a8][w._z+#RՐΖU?,:-3o8I-?qv%#ή HI)n1o/8] # A'V.iqb`5q}SPII;$rznb(Zp ? IJOi׶ :V! ^mR 'YqM.:}ճI C|'9.^V[Go9O2|Rw~j[ER)($wnq{/Qd>ʀAJR>[Pz~ #h[^ŤATIO.Vխƺ;Ppv98CI.lG=)ޮNg'@[tn)[Irk.TϠom=o|<sZJhܵ~ t^KvQ,6;ք9x[+neJK  yc*`obӺqz><2Ttrc؎[jI *Q?ዊH@:_kT Xʐ䕉dPans`yWjE^FjMl5m3֥-%:v6c HEQZO\Ixiu-㺡X2{NF{ZRjzi-Z4E 2B&)ISM8 . JUci~~5hܙ4y{h| "MId:z!'JJFQ ֕Le I97}b (jQYrvK.jJy='m{wR x&3Ʒ/c(K%\T)n% !6@_ޟȾ-G wI[-|B eT]rHҶiV#j-/|XOڞvձņ Xi %\ BŲڇSЬi3W)e3 %Dà |@ ε< ʓ̩!'ӏ>5ӥsOaelnjǓQFv4|ڊm~@I\ Ԩڗ WnQ}evwIƾٝnKE?YJp0 |Ƥ BlUITY%+di)RxMR& !~+<0R6c#ũ-6۵2qJ\W]â@ޛ[BtpHHVx;p5͛KzMmm pPϮ#: >;hվi%U gB9gBjtEw~ڿ!ͧ +y{뭭/qiJ!-@)IP=ߟՐ/aC$:$[;~;X!K`6忸TpϫӊD;\ dw>~)˥LNS>[^ycG[vzmH5qmkhJuwG-p}%*ĩM"e$ݏת:%{_ZzEHZڌQSs;Bݐz]bM>ۼgz 舊i;VlDmf.,&;4̺q!IZHD}N8v+\qّ! P!;4/`Ge;$ ^VӪEj3$oJ8M+m=7g QAmZ*RR *9#'XMM[B{\ۻ׌v.4yHql##viⲆ+ZA$|$IWNJ{7oU]5S_@޵mǤ{evԎB[i Pݜ$>rPZ_QVB) .DU -ɵ#\5~zt3oj*ZS6mGo7E:5dO6)\}SmzfvM0B"dK #*R(^XKKfvx$˶Ȉ2 8F:r+s^h̻[]ZdH2xtob6_000#>jB[]gH2[ء'r9ϑ>z+gܟF^+m R=Yʀݥi˹sm}S]w #>jܠ)Ip@X$Ze$- 8  bTAR=}mdEp}"+RƎˏ dgmɸFtZv(p-ݹ#ax mVJBJ2MJ Ka2+v;֜OrG e[iUV! i)1][QIm9ԊR()JJRR()JJRR()JJRR()JJRR()JJRRu7a}qLgC+yxjq!*r5Y-ΥkaKmJ ,:FJ 46vS*}KjBԂ(<&!xZ$9MSԱ4ڮm*1:t,dN@$n#88(+{z-s-21yũ!m(Ci%j!Ԃ@NHZ,TKn>i} [dB  F>( 5*1pN(S)gF21"$7:TP 5+Wi{M˳ wPZl8(Z²BRRRiq@pzQS,\5*g3@ CxP;QJn~{vP6nǭj5>2*R%[HNwX9E;2S)K.ʂ,AǤT Y]jB}N - IRN *kkZ@TGK+q7+\۪IIQ링߳!qiZ]h#{VG<ȚڐjNHki?ri>:`IpL+reL y a# ⮑;,ΊVR8$'L5MLtT%Rr O4`GW_GvQCts<1MS!sd_K /_rHX-5!maHx!8Q@9GwnR%OW)il#Q*)J[sӭ;@Ov*KL6<7GXX] N畝Z9$C`jwb@X7dE"6VR PQ'8Q#U &5),EN uz1IG+RIķ/Iq睶Cm-E'US)պruΙLtEM| 'sHJVP06PsϽ=53&Eݥ/3Y$Ϻo4^P4B}kT kUe(z=UR*%iPQݽCy?Dk·xz%es|%^uo8B܄,ӒN Fk#3&%u9vٷ+jD4l6sm #*tbMKuԕJKJJ2bH+j]=5+1c5dzr+HK+xH:^~ٯRQ]yǵ]Ww:nkBKKHRuNF@<5Ah\t|t&e\f*҂xBۨ;OiIO)?DkH#3&ލ`V v+<>z44GJ$q )) a$|G51k~{JG6]uhv˓qN)TC̑DkH̛KFkj3N/Q QDA̡hR9 !YYe6ی9#G WnQ"'y mȒZO'Ih3fyOfO/?TQ(32o+g`Yһ*YƗwk4-޲PkmQQ-26mu{b=*sPqjP̄Da[T{$Vo/?T=5*;FfM憸sR4|ba[>pmZ[Ґ; { ltR'_槎Dk՚EvfMJtR'_槎DkԨWfdXK"{5jxyOfJEvfMJtR'_槎DkԨWfdXK"{5jxyOfJEvfMJtR'_槎DkԨWfdXK"{5jxyOfJEvfMJtR'_槎DkԨWfdXK"{5jxyOfJEvfMJtR'_槎DkԨWfdXK"{5jxyOfJEvfMJtR'_槎DkԨWfdXK"{5jxyOfJEvfMJtR'_槎DkԨWfdXK"{5jxyOfJEvfMJtR'_槎DkԨWfdXK"{5jxyOfJEvfMJtR'_槎DkԨWfdXK"{5jxyOfJEvfMJtR'_槎DkԨWfdXK"{5jxyOfJEvfMr `4wh+r"x:آZS$j9f-6#fuZmmn-i]Hsk`+)H:^~ٯV*;FfMU:j{dE +kIlFml-G) 삥nN֭AroBi!9kyR `9=5UEB[&ߗ=sn!&u(^\.=@YH'SuKo@I NqV~|St_̛`S+_*#le|pԾڒ9U&hTs_JUjncYr]+hNTA[~~M]؞}0AA”wb4Luz uz R݈3))JWv#DPGPG)]؍0AA”wb4Luz uz R݈3))JWv#DPGPG)]؍0AA”wb4Luz uz R݈3))JWv#DPGPG)]؍0AA”wb4Luz uz R݈3))JWv#DPGPG)]؍0AA”wb4Luz uz R݈3))JWv#DPGPG)]؍0AA”wb4Luz uz R݈3))JWv#DPGPG)]؍0AA”wb4Luz uz R݈3))JWv#DPGPG)]؍0AA”wb4Luz uz R݈3))JWv#DPGPG)]؍0AA”wb4Luz uz R݈3))JWv#DPGPG)]؍0AA”wb4L$%^jdJT\z`1r./resources/demos/main.jpg0000664000175000017500000022662713623376647014112 0ustar dandanJFIF``C  !"$"$C+" Z  !1"AQaq#23BRV$4rb%CUs56Sc&'ETuDetC !1AQaq"23R4#B5br%S$Cs ?\H8&$W.vڮ>eHaYYSiY$.MQwޣָO2dc)~I&_%;!MmwޣָO3O3}=iw$1L%/i?~RRmޣָG3 ]~9+?K!K8]#֗|Z_iR1vL%,,v|Z]=k? =~;3?KG33}=iw$0L%/hRRm#ָ?33?33۝=iw#0vL%/hRRnw#ָ?33 ;3?K!K8]#֗|Z?hR0vL%,,v|Z]=k ;3?K?33۝=iw#0vL%/hRRnw#ָ?33 ;;?K!K8]#֗|Z?hR/L%,,v|Z]=k ;;?K?3۝=iw#/L%/hߤRRnw#ָ733 ~;3?K!K8]#֗|Z?hߤR/L%,,v|Z]=k ~;3?K733۝=iwѿI_ٙJY Y~Q\G_J_ѿI䥐.)w_ѿI^ٙJY Y~Q\E^ٙJ_ѿI䥐.)w_ѿI^ٙJY Y~Q\G_ٙJ_ѿI]䥐.)wѿI]_ٙJY Y~Q\G_ٙJ_ѿI]䥐.)wѿI\_ٙJY Y~Q\G_YJ_ѿI\䥐.)wѿI\`YJY Y~Q\I`YJebq\HʓӅ#>Y Y}q\]t?{TCBԈ 6=p[[b]CY ;JT ǥpV#"CFSSk/L7]q'd2tnu<q锒j,KerCuc')Ķ9NK*TDJ$J$J$J$J$J$J$J$J$J$J$J$'gH\%Ksj2ՠ HQa>qgP [8xq !RZZPc>! Amsڟoi, G5lcLrQ1Be0eB{ qts:JdHw%pmɭ%l@w|zYS/ͺmƸǓ-d˅ J%* ޜtPX4PDQtB4BҰ,XRH ⚇Q=rn57J*`b7VhnFl0ݛjRw[![Ե)l!<^uFoLj2c\ w!-(K!{RHIʠ"-i12@qm)Jp0Hp*JmB||3)Y+[f] 0;Gt@*mJVd-v Є;"tSCRckPNҔCNϱg&#%_ 9HX #'g5K&d' YXPS7D|ќRWB8,T_+RJIRJIRJIRJIRJIRJIRJIRJIRJIRJJOŷθPRԂC#ȑg88+^.Л)Y-˫P8 ?uGkꔥEGd!F i<(ϼ,VyPŀTNHeak(Xpy6h4t4߀ǒ+b* O$~NB0G(֡ٗk4& \m ^#J /c |$8S\.evI؜KtE;}{Bڣr2ApTFoWYji0c-{ao+qq:cB/+nEw.~%5aUĦ*5"dڟGi\,Noui9V+Yf._p?}G i~_?mx'QIӰ.Gj&m1rAS,G9@uKC.ZKqh^T/rꟶ(wkܝ;$ݢ9 [z;}䴕4qR 'Ŀ)uh~?9OK}G jf5vޠ1y,GKr5HnUJVn| \'*NR8zĿ)uh~߃9OK}G jIvoemE66=Ka_;eCnT/rꟶ([^j 7ͲHd=2:b6 RIVNTz)u~#R/9OK}G kRv"Jc^Nu"ҤImqa(+<9r5ToNvs\XShSkaĶ*N27ޱ/|]Zy?}G i~_?mVo+k+LN%rqA JN0M4ft&bߵZw{vdx%KC _?m/>KUr"i7GRRw EPb3$6A%ai>%KC ?/rꟶEASEDDZ§|ڜ ǘ%KC /rꟶEASIoٴ;M<+| |I8oP{k]Mk>bZ1>fEmn RTz+j7X>e.<>T/rꟶ7+ty-^/ŝ~QrqqcGB\Ȩu,Cym6T0O1sK2VAS~_?m/TĿ)uh~EASh?~ڄ?Xb ByX ߐ(T/rꟶzrin*jD7[ 8Nx֙n8.we^1 u~#R*>TQJX>e.<h?~_9OEu~#R!>TQJX>e.<h?~_9OEu~#R!>TQJX>e.<h?~_9OEu~#R!>TQJX>e.<h?~_9OEu~#R!>TQJX>e.<h?~_9OEu~#R!>TQJX>e.<h?~_9OEu~#R!>TQJX>e.<h?~_9OEu~#R!>TQJX>e.<h?~ڳYvIi6{!Tx'ZeM?2|WD1V܏-MXU} jr\Go,Gum~ Ck) T=j9dlb6R6`,uYmLqF?ӧ4֤}ٛn}DZv11v$g2pB?umZWJEnEĭ 29 pzZnT3`'h[j *<'Y,4+ JG C Һͨċ(aղiK~D~vJm6tEm[~`Է-wZde4KBVU!`gxH:~b?ww=~pݝ8<ڎ[q{6^u7}:m HK[( ;F҅ @NryC[){::~8j6$4m ~[DAJ@ 6wu Z2D*y<+ K!)3Vock{$t3RETdfcz5ͥm7fU m%6+G|)d :Ca($1riVXܴ jqKq(ކ!; {#$g"1nN@[/@*JIqPS>e~ފB;=;.ϛ^3QۛMٖ (8UO G6-ۯ>d$x9=MIv-|1!7Ɏ)+HRSLX ; FL =Jmz70Sjy&c ;-n-8Zw x#=oD*Sc7R}P=?z,װ!IYjRH)nK4ޫ!=AGT]f')V⟳>_)>GWr-d:sA\;eϦ>GkONn5Ů!D9PH?T*TFe'TJ,hKOwJnݨsq/%Jm'tRcz4?rj2sSk֖t 2R=H:DVD*oiV8!*qq m UDڴY a}P OAQS[i-9i?:/Ö-SzfsW?AOdJX;P^Xw4i@ >j汵,0!?L517w*Oa"JT4u L#8 jeZМ<OMpKRsGG445堏g#N^#^Q[y!]ϑ>$uiQO,m`?V_gBj¬*k{L닦ђV&9Ś3q8ʷhn)GТxO=P w\Ywn3m]j# +6;IEk!jv{lBQCoGhB@”B s7\ 1#r8#̍~HdŹH\lսek#C;O$t˕[!q-BsNx Za{ӕu2b̞^m?kW-y7۠";{JZFI gs5UMjHs$Û{j!I}##髭kvH8[dw^R=JR* 3pViv V>ݛXnh[*i\R\QY#bBZic'hH>pgɈUYZWAFp*PiD Kg<j}j0HNH<}*\gѓ TII7< (zUk#!~OvV6@+BFxy(_HGf4 RP|NٞbpIQ,kVH{T}Q0NNK~$oxT8?U~P' jc?cApfM&;,d+# ջ(ϱ~XՐ\_{)AX>G_pM[\I7涻? !? mŚ>0kp:䦺1Śn5JCqOr;½@;>qU ֑ɚn4 yѢ)C'YmðSJ[ )H$Yp(U "&% cic(JR6H=戦(RҐ8 V8U{vqIll)PG}Qq9RH#ȣ/Akn,~lv{KqI#'Sn|1p؄zt6"knC|DG(5=ه r hCh@|`fFڬ)'CuF4{O>xMݱU$'?ՖAXژT}p8RC :T^Scf`U ^Veno2ʤT}P=n7O=ot-W`v>ټvg7 BZ6ÞΟ0.1y1ЧH@JwgUZdvW_5qz]=m#=B9ޮ[TҐ@OtqYfvLGpx%Cyޔ=qi৔+')^e:(ƛl[aK JTrOXwLg#v0y<Ͽ˭H^V#n%rWRmR69 ;4T䨐A7`BGBy 7 \d%<pPG`0oO(qŒ8k? +;Hb3-DKZv+{ZY1qYJmd-`*ݪ]Ҟ{twr ʊ TjYPD5iyq,$CzzTtiiX-nm6oFA4Vwu4bnH%A =*Ϻ:HQkD_unJ QeT){2emcJJ}^(J>M]ݗr _ݚj/fii>؏ JIiӜz0RА9}lRK] \Rnv[Vђ5 ږB@NFV}YEk(ISo:6?] dЮCۋZPF 80B=kx‚!ZRr(:3:)JjbcF__NGvM, oޖDuHa*%[T>vjn-fy|uP< CoպZ-pA86+>CX7j;m%lSpwTճIk݆HQILa6+<:NDk5u3^̅|01WHti,㎸R$OQ=j۵Dh4#skeU'rl,қu aY$FҝQP%ΩVZ9 U\yI\%HڢU)XbufW2SsX6\[>PHr }M;UVz,0NEכܣ.22iI $c9?M3-"!@R S?O*q[z ƪi[^܍ujZbd:TKLuR}j=GJ]a°0T0hYosuNIi6̇7WcŅƞMc8Li#Xqi%D| If5*OT3\t-;1y#\> PIN[p)VYHO75{m$=ÐEVh?H* ykcb!{;HJ @τ9yֱ`jFHS 7޲7i= '\ж1Kjާ#8Ǚ}!;ʲs1q 3`>!^S#%6XN{UYJTGPF%,#84a+ 7yB9g7F=W(e^6r:έ, qPYr.l3kc ">~鱴=$gWHA {(Gr[c޶?SV]fBjƎ )k;H"-Ś^Q[ܱ׼NsS܎D|{#vd3YYE0H'־f?ҝ 9"BUO ej)[ J]Cn}ŲJ[ҁ N?y&;otB[^dֻۻ+ԗ m j╼6Td!#+F[cX]AenxfJT>h2Sf.wX%ZB["Zw,yʺqhy'Sĺ\.%mtN1=~j+ian)!H Q2v4I9YTU!qڜFpU9 FXG <'s3ڱ#=/5{P7-2P;!TIRRRi K z |^RnKAYR'$S-lzLy-Iܴ(- AWڢ*.#GZڗI jDXtPVPiUS{HZhPrp OAү:r,Jc6m?VRz<짤K} S!!9=)ji&-9*wPAWw -=-0{R#.c2LRZZ6-[&$l` Gm)DcҚn{;e=zq5犚X_# U~_W1qꜝ3o\Pʔ4*TfVʼnܸHޕ+O')(il]Gken6VMN95aum].!ݰm) mRO>DTlTYkРчT1zuVc)֥*Lbm JH'>&I9[H1KZ`nE?,p>5Y0ĉKJP5Y~粁1끚QqTUS(S)!ECvy)@J}zWɿe=Kb/m$ *'-+u%͆[kbsFvlƺkFvv";s]1Q(R=9u,J0=8|CR_>Q޵FM.q[-cQ߽kea3gpOA~^5ܥϑ`VS UC/L+`S0s*l ,7m;=+u|Uom5v )~tJO9**L\̹* !)Hn=~(m:W܀q7ฐF@=GNBqq-mJ-' NJ!]6r$h}L-VqUJÍS7UDrq:}A6NOt*}DD>#Uۈu$Dhh[pE2X)mG8?:zSm]|a!um.$a=bӀ$zC0B]7IP(;znh_Bm愖Ⲵb1LrH-3˞3eKLJ-r>E@NUNDb3'jJzLQ UEZpyز.l )?5Uc^F>\Dhp; Y7V4qM\6N3 k{Guk{(w*&C)N<]ۼGeZ\II8S`•g|i1aIAxuFgim{A2523FQp`FŧTCޣY'~챛QuxA op!ZxTS1ǸW,v(!ʉsjDN!l&:B_*[4l{#~&JI H/2J6`u#{Vxe_],y6ǟk I^[A~;O.eSC9O~O#+seIze&CN0QqҬ}jh9#Ϗ/ֺmphH\eLuǛa%JA8`xԇT}DKC)3M>$dy v Vv8T۠o#GA:?R y<\BkݮM;\u ]ݞ3q)NӸ$y 5\@6qҝ\e|6JԋBi:`&XVjNոw@ s>nuNcݠsC۔Y4[[2[qҴmX >]*TEv܃.suS.:_)-#M8C}9dop G6X^ J`gΪET[NnK5=?e2e+Z-sGQc0sP `}pPɖ;xԴrrl(tTD 88xO) ~˚[~$m{#QuP۽!֎#vlxaNp▗mVO - Ŏԧۘ}hR2T*2쐆@g(8vC]HKsoڋKWIP_RcEy))'z/*7Vscfky\!#Д/b|@<ӓ[:,qy,F<ʔ=~ dE^A!Hq*O`ߩi|.{HH'UHG(uӊv"-eZdRT`f J{n-c2 ]B[kFҒ<<\"~klivJ u6Tiw ' A6)o (i-#מRsj!a񃎸BPѹJ'9'5Z`QUwQ?/c Zⶬ I)!$NW?dLJ;0xlgp 9t!$^Н*3vn$S'm(ʡ&,aYiKΑIc;^zb+bΧ4 tws}nj0;Yh s#0E=;N aS6R"2}ۀT 3f-5UԤC RG2ڏbН|!i|e3-N:(gUvrx4chr Q=c<i K6?-ZQSnmArqFݩŔ>\ǕZeØ\y%Je%Ayd{kV@m::Sqi eUvƢf05ߑYMR\n&-+CyL P6yyQ}9*'ҧXՆP<9'nq[@* tr;C?*6C#Î9@h#v1׭&%i_J Vp1j e9u8Q&lx2RݧpzsE.)!*jiCm;sV询WI9gPs廥<?XU[52sB OyF0M|ʳӳ#Ti'wImH\w$SkO |(\+ 6JxQR~ Jv'΂OJɤ{l4Ic] ȎR;-8G~|Vr4vx_! CQ9TV䫻@SB*;"2߀`:xښ0WI$m<:TUT|_cٓ!yEJ N8]j/ei{;m9 {HVCrY>!G?{nUWfWz3q'@v J- 䌜cTa94F d5 Krs\7Zk4\5WYAm3׏U+DG'"gKl|Ps6*%=Բ:ppϞ}WjizDw*Je*N_] :{֚ݚ'wHhj\{,>1qQu'DY$U&޽ɾc7C868,9̏:#Q! N<栖* H>[WyPN9<#믎rBHH<W.(`g~1HG7Iެ;ß%X^b̘c1)J'Vq⩻3H~C1U-.冖P)~% o|S%(%Y>\y/J@8U,B,9:uwOFJn2`$,```QljU]Ͱ[CdR/!<<荘N8Ej3]Z[TBQhoOϾj͚nˁ; OЄ7`eG)yi$=q[G1eL`pAJyCQkg 8 FmN'$5ηԍ-v~֬m0cqIi)S&;Н xAi6GG\z ]e/j2 KE*%5r pm8<UenZ؟3_p.QTu6(l6QTHPBp)$Ҹګm9ԹF1$Z,7/)'e-@lyKOJ N;myaIXAjVB1M;ag:epO!Dy'UU\28WX.WFa# N1%vsVbH=<qyEM_98A1[?MU}\I?jl߈MXu USDo,8니߁}<9Śsr>ei 'š{(w*&g<)@Y>\SlIGWYPT'q8$}Lfv{dnJs#}דS'>Էbw5'ۚ[+iIIhbR-hdzSPjʓ>ʑ/(!D`IC^ŷsY-FBT[ NԀI'' `QITvZ;ʐctKOmѼ@JxڒRR?AF$s柶Y_FK4)|՝F$>o/p8ʲ5kSjv~I}Ad}<CmsN%nzZZ՗K#~ajb5}Jʋ9]F+v1/oPN|qQ^u; }u6lP$e\&-]yu;rI 켐w<|+'MTFk:k;t\ qGsnڴ1g~< wōSLlҺuo6\VJy<,Wϯȹ`i}7fML$@ZI ǿS8lqO0RHHW_<\jɺL{gs"[LRmR8tu$ =3ebͫSۂ,uN0G#r9R SPG3.###4ۯxTA);Z5#!>#ʞJi‡{lC7,nx1QRB䚲nsaV]}k;jr4>iǟi1d<Q0 P(pSHR fױh-9הxn=<`c4UMNWidV9Ԍ:c{Nyj`qT%*Z2q$:H+ڜ;HZS% &~9eTJ@Ic~n<0OJK*B Aǟ9+# mǟ7Y(q>u zz(%,2_p 9 Ty 1)ԧiwM`b%*Lh*aPl8ZMfarlA1r $|*ޤ;qg֍Ky"vRi: rWl%^.%JvyO>j"koaH4ۯwsqH8 ͮoLnwX+*eR2)]ޒi NrP+aW㢭<- ෛGvl5~TxT?bnP*b:!_L}|0??m>jqh9n$dnI;A S cwVyWQ$rʼxG !DdF3?(š[xxa)+H xPBGw~"rgAIVc~LN3r޺qhF[zPR1P]xI x}ZɎ,LuRz%R6 ԛAuHN ckv dc ]Tt{ϑ!YDƘ㙻a 6uP%)vgku Vٰ=ݼ¿O+gcioءFy#)R<@{ϕ8'`Hp}Pu:ԈzB!ķVGC2RǮ:{sJoKi7gw'S^R ?q?s'N)ĥ\#9* ҡ)En>U$lnN֖Ad1ϗ?׾.3QƋ U 1P9x `!YU)j,{_Ie3wJ.?1#(,FkI;?z|c-w~P5vXsgiO˞>E_-up]mp< {+n!5cU_&j\BC5ν$[ܽ7>(n)G½;ml$dHh;['t'zF̔)ݏOPo|P`Zg'rT@OJ9E)ˇ[OU)\v VB6vӭ*V(jO'xG5dN~'w3D$p :ZvojbxxOA#?u[ؗzVRꋽkl)Bs5sˎ rCTd~ S(X-g\g*vl#TjjVͶs #S: iF\N"pD)m'˗ۊvs}սb qCT8ˁmG/b,6)`wu2n0!2#GT{6uݦUÿQpR H$l uԶf˜5!"8dHC-8Ҫbkm*&W)En8o8j!zɭxۅi%8~pu@{А^`cYHl'a]njL&$ e'>g̟}M.O_'kmCq%*bQ%! +'cy~1pc/nYRUVddneA}vދh|yDěx6^:SEWiqeHjt-+? sZ6Y.SF kZBH89_=MC0{:*x]Tdq^/z.I+kSl>b8?잇>Un6,\qG[b]nN)XaO9*p~1I)a5 m:M@9peLm$-A%% Jp䐯!Dw4Q;Ch#:yu@̙duW’^|;I>uϺ,Z Grȏ#x\s=:CtzPbݤm=ga,kX)QXqaC!*rR<뢭<@cq>W$nڏV㼖brӎ4>5))DIь*>߄gOOLJʇEp|J=`V4~С6)eP$dsO# Da-Glcj)ʵ5dz,DiEiAC#'l7YwV%N۳-$@ǚ|Ǥ4^@IݨTPIIjs{[vt9q; Inl {m!.eQH*#>bm';P|0,wx99ds ZOs|[ )C̒xq} v>iXs㖋w-GU[~"OKuN>?V7^@m-2UW}6 G/Zp ?tb;$a;NClWgqFb*=JS@O(1~h۱HIm|8{lgfPÌ>VrP||_j~/[l¤L=JSn5˸̽f!*AD;2\  yE." kyFC 8AА3҉ay# m}wA1oH!=p;1ǰZ_%nZ.3\Wx eB~'*%L6"-Iɐ! q`dȨ{e̱Ƶ*Qp;JJNirmVfj.wˊ# T i*.4:jٙ.P5"ۍ!ut5:{LRЀ U=3Wr夭|UJE&; \x{nBs(rsuAkX(s}Ia-9THC{#cT*yzstTr۝9lOqK\lРꛂEFO<`ԕ uc^J T('pWCb)؊-jHk=aQgv較*N?j߼vgl]⨤J>|vs:>bQtgȏDs Uv[,Wi\-ey$:!7׵{i{0 9Aժ*CuͶ6Ect5iT?Şt&OBl)֍@d#53_9!8 jݯT 4D- @Zʀ!`icYݻNye'NsW? :'R693wjp6oC:B|SM*#W/ǐ/UjGQ*[cp5--2)lisgyc<օz!PB~曲i[ /MHD!(m~h:U˞2k{kYF\ HVYi[S@^e<EЛZQ 2wsqu(m^݅xI vFxh׮5=SN["Қ;]RF{IVswik^?(SL H[e*XuZ(ac5ϖ 諟dn&իٗ$wVɔ#$gw 'ρG@:@}3"Зjт 3]qWܤ6)Y\SsQ,뱔O Ҳ3jƜ4x3pd7 "~A7>oaʍ8th 9$IOA=+{LMܷl;vR-[# ~R{pLjKSjBur"I^r+NÿQ;(6%s 륯MKZdEhgmNIq_&p$陏i(Ukl%N8 |ZzbǑ:+΄뎩6g9n\v\fZCOg'lwssMHo㧗p\9;@$*& U.cM:m,67>.'}+d-%\6,2fЙ|춛=GCKDQ*L4n^G?i]5u(Ͷ<yϛHvRmdeX|I t-OĹXm@[j*lj;<j" >Mb5=i:]Sٮ7;-iZf*:U?}D,c=ҕ,@9NqTzBC7w\r0$d+W}i] :z[EmזHc#*iT%;FWl3@͹ogQ;QU+ 'c=G9GᦹDxk'Efw-c܁*<BIꡭ[pӡ≗bI Pέ)`9 ^=v?l(_b5ZGnp~X퓋#t5v]~NQ?Bǽm_&Օ@r?kCV5p]#F״_u=$PS܎%{y3I=(j\ZB^ dQ3;O U\i ~~< )]0XVn:2؁0ڱw$?RmRn:r.04NA}*GjL66>ScPwOF!\m&:bTu"du N8A99ǿf]@'SJquO2es8&kIÀxN0W[1fj' > I{GЈ]_$H&oyM;>(#1EhUGGVb%42Ryt~x Ϊh'S w!T4;_WYmօ_(q d{&s#ˏ98TyNH6wZC-b2`wuVkfm@1!m<I#)<+0A#kOS>@^Ѡ܋q؟TDV1v[8Esbӑݨ/1SgV'bʒn-wQ}26ʛSL@ɍ.)CW>1π|\vmv덂Ǵ]]B〠 dqϧf,q.^%Hc㙇/lsSaםuG>54rܨsm1g6TFoŀ͹H8%^a-Z"c˸NaZKi'8i=j <9: unC*Iq|Jq<qj=(]FM6;w9E6B%&d-1Przkkp}2i,~Ѕ6)qԤ:i1-)82G -\qҘ{d<+ ޒBGk 'IXd<&3x Ƿ Db#3 rSR[vsT}( SN2M_tJҟpǕoq~[`_-=*B #3#c?UVY` 츒W!A[?;>2uy 5;hO;t AD>fAsmw ڊ+IsWăwltjDO ux}}#!nOGr,_*m۵sZkm#p*Xϥ s-qbӜF\v!]YEۨS2JKBʼJ#vy|zT=™oP e7T@I#8UPmzinOV%vFJ|F|9* ď5SCKK9d7/qM~QQӪU*xZ֧=IzakLU0 ;ONU>l%;pH$=y҄WOp:}u4h]VшڡO^I+P\v sq('}Z(U {CPYƛjm,\.1fk}xI+Q@<.p’Nj}5-Xj;))ז^RTvw`v0-ڍ/RvQ icJaiR}AI)-*F8ynjƮ6kl;>((aJG(?kfgT&F.TVn 8RTHfJC%#B*tLL:Tdm̟_;bwSLLZYlRVѓ y=L)k{t9bSQeR[sciJ㧡F:]˻[mO_*{3??lwK)oީ*#:ն>VĂ{vS.d.7WW= gfk.6PkxARc4ݻCA"e>%aC8<8l2mbp7&"voy+߷_)o-MPĴH@i7nx9Ir8򡌪msxmǵj!v{FÈΧu[eOZZI!+hH :q3"4Fb׷PX -8!9X;ӬU wHN䁌zVxνX>VDsPNx~HU1-'m]F]07{|ƪXiO2mZ e l6BQq% Pvtݮ}{ADd!I,v+7t{06RRmҬM2>w^F(@hzGE ʽHGɥť!'ʛ./,d9Ā.A RK[SMbƴDW4\GvNp3rFɠi~\(>J3Mm?Kݯ]̙;Hi;NGNx\sFTح嫋 3ȫhAb6w6 G\>\;O_Ejhv(uE+ 2 P03+nH- ,4jd,CFwysT/P!xx5>ޝ#pP7jc .v*ݛ]!(g.TPR0JFO=3O4o6Q2S.Y^Vz˓!Np2^phb%Yw|*O(V1e 6 &$#Z-m;-u7# C^p(1,PWfȸݻ!1l 0Ty:3l.ݺ.f8sV<{y%C>9Zr<{ϕ+s`xչlqŽ~?h(qtdgk`+\;k_=B[} UV?YQ7#F״,ms<!?]#FӴM~=z~PS܎D|{ /@#Ά/+%IH#Ϛ%VrI 6z;D9>vC9*6Z qr@TwR!܌!e )W8Fť P6+5p,aM^EY5sV) $rymcB^"jqƂKAp7$Iݑ>U˵ޣIܭK 0/]b^W Ǝû@q![t+F4?Njg"R N'58 @ x.'cnGea=jC{# 3~*ei&].*4p::S+7[m R[ݵ&];,ܯIZRA$gU9~(f{O[û';ps Wh, LvLR$ouԷnM%Kv¹),aRBVpwOL`~{l谣8֯ƾ1rH>AnQV93UUh6YM2BR::'Vnc~ORϾvGz$U#ҦZas?msoem*)=GxֳlNƶBSe8aQ}&O,omKR>^3$Y/o0&F^JGi&|d\ a5sqsma} zSpRpqR&-jФ-n,HRsMTjm+Vˊ̩R* <;0L e;NCKNf!;kqOI+X/guâĶ~BB8#}͵? j,^mE$}3>N8xȢ6.!ALWY ipjs^uJ&|GVW)fr9Wak98sn0ycS17 N|GP rpTH$*lfH:67n=VhVC0mĺr-XFOH≥iHaկX/\U+d-Q&~Δ0Yp9%#wcS6P! آV1׌ȯ#nm~v 4 n{|@뎼ӝzFb왑%ARw-xQyޕb㺉t)qQ!GVنękJZw62jGV-L+͇8%q/A` 'Vv#jin[D[ L-V4'ciW'a5"=/m`'Yw:yR dQ 4I)J0c=|ϝi Yh}L;R Lu>c<nFX ,ce6p5(R:l7i뉊߅-y2~P;;7-n)a.,AaX8OP9h[L7 uLea.-s#Iܤ} Jj=a@aj<~ץ0:8ڶ)"UWCDs5˗ˤ OtIHN Ê>]r4o{zjcȩ+i[BJw,Ӗ.vhOQ-F3F=K|i $9{)N%Ehct=n}=PUkHiv!Wa0JH>/[%9 lwd +uW Ot !Xjf P [IC0C{RNpvpqUH}lP4:i{[_~KkH}][-M1zoލ>Îo*G8?I6oD>.Oբ1-b#i֎珮+a rQm.F>T:ylW ׹@ U=)9rN>(:|k3+#O8Eih#~wgBj]ܞf` *wˆӴAH$Jէ,TQAFd(ђ}Ml=~%Ì|z͏lu|?(?U?BǽmO'VU[dB~eG܏Ś>ѹ"35]ir'xCqOr;w!a$yzPwp3 9I#>ºM4͠4] |rG4?m N3%3xruz e!*-:C%I=t~jms*?p?}hnvD4׵Nx2Hȴʖ%c;\-N&2oͼ?(&EaU'ν䭐o2S5ߵtk{5a#syu制h@`EM1 X6uBNb8VHDe~ʭֆqmq ROtըl]J7mRA {τІ:a}B8!}c4R2p'F$p'UHs!@R3jKM%BSП;&el“\u&ë.KОBpEFk.,B G@׹K cjBP㑷#^#yrPG=!Hmx[d+Sk:;SY5LIke=|Np~Yoq"jy'`-.78C l5$Z\{nDN_SO #'MGX#;f!A)suK'W-^#wg~$9Ϻfnb5ֶNKMĞ>ZFԏYB )ԯ:eZҾFmyZ%KR\Q#$Nvzĭ12j.2[-dG|*D Y-ݥk Cd̠ЧSkVry;`55oGZ|R^01#b_qHqҴ5֥;xa Ų($d}Wm&xB[ 4%;Z"Fɏ)RZ@A CǨCP0KEos.$rGЬi$EZ.1ICjTy V@8Bϩx8?UA.us $ w%-}; GON1Vj$pR9UuO'fV7c楤Zaa][#0{jqǔEE!n>P HJ#ʯ.\߹@[YqR{xP~/IC#e9|'' dֵ=m0ECrV2 ǦzSjs69m}m7@%ń"Dž1=٥nJ_}/\qA.b &{Q*X\jg T 䫡`¯NB\4*uv$Y+v"KL\9in)DsN3YZXwI6 k}oqط8l5)a) ˘[Xv5^iVcY"@ wAspF깍ʝ\=U9&g:PpDC-[8BV]GQ%XIm9ִ\1ŷ6 z[WnX{ˁkw'iz~x&t6utQ96`g uMdu8fi/ZXn Ӳ4jyDYs!I?ZbXv>|>H3Y}-)njpa)u鱀I*Qi+w(N[e2x@mڑіrn&ݒxWM";2RM%➩CVHcxl {yX+euCKs^nᦝ(FZnP֬4'M= VҖoa' rNpshF ة u{%*$ TNs}*_a?f7 C8Pvtu!de# .أь ǐBc(Wa?m¯7.[Ke$ӜN!J=q <¾W#ƨEeLU98Aձ7)Jm*Tp~¢9=1\nnDJBy9c.*"4jFr Cd`Ӎ<dP2QJ-6sU")fQh7,N<(Twu:`]ڟz d[[ c;35mO ..dq=CwbOrLvY[NNrAoUD8iw$vc H`ЕH(VnoAJmP}j*b9ROLӛQpЩMnsq[lwzM9ljS2:%eӵL[e#;U-Ҧ:GS:6oz$O46Д+hG!@gtirʤʊ”<$ *ڋ;zVM.n&%wLQq{Fd>X :EF4}1Ȑu^[ds1 ]Z5ņUw^1K=ۍ ei *NRsRh55ۀFaPm{2OhwKջR[m oK{D^KNc%-5#(41νɶ~jlnq {$,_Нs"Zgos*q{JJy {⺫A,ZzOPBīb{1]Ic9=vU/ t&:)a)sґ{n2uY|ٝPuC/bڌ)n%I<`G`FŲsqG+ͩ$`qvz"vu1YJ G+NEwF -w(#U22!;ͬy^Z[sw qFVt@1Tns-R3Jy–:Қu*GT܉d޶@:Qbb9~]LT;p̕rK8 U4p(JukW)I9} twr R #AC[]RJBO_xPmuAkMZa}7ZZ0TpAg$8j+SmAD3@LgfI<(wWrʈa(p9uocw Z?l(Yc'# <&V=dFN:%_5v[f^2FP[ UVY?\9ιk~,7vq.'C?t;}><uf[ Gq)%)uʴo 秙e=& DڹVW J[!L-;ZHv;:IHVB<s @ 9ߪ3Jr^p#bQ9ZD@HHT_=d):ŖәK?*w|U)ig'+I.0e8m$[%oFԟeۮoNޠJH;@ a@F1Y)Yڣ>69Cǖv¹2XBтC>!?5?֑g+j0XEꈶFFuhVwƢQ[@)eK#ŏSTdkE}Q · /#‘ !=#r>^pz-պ'r _-%Xܢt3ռ$̒RB}O çv+s 4 !yF}1WLh F`lT7>\ʔB2{1i}(.2҄K?4T0ғmS1֔8@8Y0mf|as> u ;rFO8: ,6Y"׺[(B\p3YRFFG|*9v^'%bm!jA(>3UE&+@ qx)=*5ٻt.DJ@}ijp V04! }FCH [ݵ>@ga֞C>S$3Iv#fmm PF?q֬4WiMT&3[hVTd2Lrp>>S >!s Lc`_e$wUt.-ipᲳPZtMw#,nzJCMEJANz`+hf&9Ό`HYLD!.̩ $PǴ4IShVIon#Y@I{9έiP־݊T4D9m-ܟ-r=q@TOKӷpH2Cv3Zp$d?Ou%* ѐsM*#sF=j[cA;}%f樭gf*[xwyy,º>}z#[8mg*+Iϰm[PW еo<%ˁYV_MބySITSZOvP)I䃔:C7 q!w`jnP+{7Jek:BR)nGCui ɇq%JJ֥mP#H?XzFi1n^i-=AD rB|GP3DDCpwmTT1-ϰZIZnVώ$|VܤyJVӎī3+Me*ۼw^cНЂX^_kP%[č8RH䎞uZ9yZzC}+wem(V} vy'>YOc.n,Jm[ Ҳ[fXgWs*Tx弩 <4:Or\R} dp]xZ;,2lctJx澠pxtY$J{0Hu^QP%yYT?s{fTܼ)#‰8<]Gu^`RNW4z9ַ5>ڝiIRq/#V eEܠG17{k71TV`HmӂQs=Jծ3-2㶛绖ҟ;R:|JS }ݚ>ޤ6yu)RJq;v#1%HayBHV0O𣧪B;#Ie{/ ǖѼ~њ73j,Z)ۀ>O9V٬Ǹ0-Cn%Km]Rl,pnIS->=Ƿ@t>޺{Ӽצ=+kFlZL D' J)Q$2=µ~,51jgH14.uFηLm܅ (PG=O4V=]yEaDOW7tNN.laHi GvIQ'' n.\ ]KHB2*CEm>9q3gU rO*ǜ]@ ZZ8==wN}H Hiݹ361'n98Gw'\ /shujqK|܌UʘoQmWV`A+ޡ6м[@/xWLyy=!lsHۻU\#wUTq[l8JAOB\^T@uUZ[KҔBR”3{5R:lI.ʕ%8A\*eJ)4F﹕Y9<pȹ%E#!P u+R3O4U5XfvN-SGks%>R&b03eCh+$%()Dx Fn a-VBz''ߚ=Dx#dXH Iڂ H8O=} 7}yAq,wLe 7$zF 'ÅӎcmΤ 'CxMil'@*aKAJW1?M2|<'Њ]*  i_2' b5vB4qݎ>=x'J?h*JMIZf >?F@7ֽِNqߟ#GܨV5gUO:6&~,7vsH' \?2{(w*"JRSԑ}vݕ$U½֍|J$%?AʂGe\LUHڼ)y4*P]iaԴw[AN0 <ꗓ!*/\qiR9<:992 M=nV_n׋jgE㰗ZP7wG㏪+A-2#RBԣ:qfKY H]{[ͼ$5#v^[5]-Ŵʒf-2␤/d,U3*fa}nUµv;)}ӓyIݎ>v.%0lARv8x?3UL Nm4)6EJu$((' Yےړ|T"&drXp$V1a72 H'qu벙P<$J-J.$ kW5pyb }-@ eH%XO$sK@ɟa iOβXydBuIMn2)E <xjIpW þjY>av}PZlsnW(ВА[ਟAִbkEmħ uQ姣yh "\PSe>6U=w-nMLs~7tX..hs5w&o3,p/ T ‡/gߞ I%i*+suZ CLM΋hWm'2xDyх 2G]SgMZChלkIΓ9Ii]Ùl,t GEoqHG,okO5V:4-5nR-qݑsyB]=ڀ' GyۭD!Ɩ LdyӰl-r/d9^ ZԜF]-̱ĸkV0v8qU:5(d`m4?j;րHV+R$NqЍ-ߤwŹ!SIPRnR'pHTRJoKjЗSQg]h[JFu-Q7 wI`sU0ֳ3߇j2:a#Ga";iTK\+h eHs#C k׈SˑW{:^JeӰ+IV6;!aēQVq5-}2Xcܾ2r 9Q" Ϡ4ʉNP$ Cwha QD3ϻ\.رv\8vၒ^ @+-0SзOu WIK1 WjQiKOt:)ݖܐ@2#Ќt4'd]5e[b 6h%PBxA#<*|M; ޶_~ޅ~Dw,HQgW='j1|~RXiT06 tQS4r/ NW m1is!.HlLœx>Z*6s,(=:`K5ď!įjF8 9VR8^a׵F%D}թJ6 q*zʧPJ5i-Jmn!(ZV@P%$t >- ođ R7<CsjzqR*5G) PM+M_;/m^G9q[x@]xK[ahd08*%GQJrE4`.:Lh2nZY?(2HG=|5]L6zˈsmd MuXm_@ ~է' AHeJJBs>]*(y{:J++`c}FK׌վD{9u$8#=xVWms]u-eJ/pݷ>|&۟@mbf][&ƒJq>>[nTIh}7uқV켨Tɕdݘ]nH9>IuuR7bJm ӄl [~ mshdtQQ ۫y:Ϥ;㋐J;$'z:םA$Ž&/XO9qW'pDi&)†Gj<\tCF3/fJCsQGKq G6v{,J\-D[ԧ .g[Q PF7q@tH~vZ{vgF=noE .:T(x<63LIr<÷쎔I3Wz}gr$2Q\ :~ۑSj;Ӟ*@spRGB PeV!J/Gq RJp8d~UJc8q{1is";/FO$tֹَDu!.T+l{dΚ't՝@r?kI9wQ!ğfY?5]uywCqOt;}69Kcnp<]:!20Ÿ dg>Gi T%Cg`0@ 4AG>).:FI}Գ0F>Ɵf mD!'>" q7wN RIZTTI9YJ\LPmJYBU!%j$!$2ZbwJrz8-hfxzqnK.e8 G@sO}Dv+.b"ڡ)(* I'”zҼeG`zQ,C8gp*ܣeNn)* c=mI-wTs"3=-ʔBp|e$6ut}f )n1\l|(nPKNyk&c@)[HJ)z$=j.:nfWed<*s)3!r&Dp[ͯ【O9[֠{RLx(V6Lpi$,THrWr~ ZT#B隻[7T%cL$pzյM'[Dp ޲8 k;;i땕2}qˎeIRR>uzP&hw4ߥ lRx'|' pqЊUzZrf4{Ē〓3EH- L0lY}@)?임j]jcfNq >A +9I*&3t NqvZ@Kk8a=}K(iLp6܉W {ܿ|6ʼn9h1!`8[n s u&2p\53%GqNFs+O:9O_ *o%# =kΚc {m!1&Jld g`3]zBzz2bhumTJR8$<2b{A+JNH# Q躯TJ}MH!2iqC+G 9SX71H"GiHe)p'y+һ̘)U3>Ų˅H%'j†A I$[6iMƁxDxy*{r2 欥ciTGݹQ+ d\qoMN>Q9{?n(G#ɑP㩲0@'r2zY!YB3jEK6OHNj10ÐEyB)ĉN v?kx'8>GC{%x@(I+:rV?Q8O+s*sN>"n4X/h?"P'4ˌ)kN@5T|ԕm\.:R-'v]۵>3WU9)''6k;H$'K,$YBp2IY柝fr-5ީ6R(\Bp5{.=&mWDxb}nsDeBP$8zVcӘWs]g !'ʝeDu0wHQz)'8=hFCI-~\жwB䖜Iy-$e c*MSia^F'Lz,4cg  @yx?G,񁴝bW·}ĝJHdjgLit26Yx=2J݄J $7jM\\ѵ[qʁRKHaM dGiZ[;6nm\XfʎZ}NqnwqqP]8GmmrH.Mtlr#2& !R1䠓Tke'2HaieHx 'yP'M.?tM śl8" g wc;ܐ|8BIܠ7 YztrWlj)6d^6)$xA݁P`sA5l7r"F1JqӍ $nzػBkxAdŞL2Y~)Û0r'+1ʓ.=NAn\I uB3% m$9$O$@Lf,%pTҡ06g9h xxTr:g(Lm GSsS!(M|O\8+V<1P )`cYUsU8>}1֨~R*OJRG^CWa#<MkݘcX9 ,cNs|&̹eg9cT+l{cΟYe:~gFܟӂNsu#a5ҲkJ\\F?U47#QL'CAUGj*Q  ם' #܆I$dbHO^5!'1kِqGw@s?:V) /t  t#2䶻>U98[ >ewUۋ)P x~2H85 XGwI?ߵ>i^! y lqWZ18|S++==I+ &0H#RiD•ן_X1c#̜ ܤs< P^P+ d^[uF9i)[k$V'Ĝ} z H+V*?H8< [|5wm~RO-,>@uBdl)Jc ہE8&*A+uҬ$ OȘy$p&8A{H qYN6G#άm)ʈI QAP *-ۈӎ|XU$wE8ZdxAS[}ӡJR]^TVOz1מi{[ExʶS"[JQ*XR<▩) NH㧺}Ta9ޑ\ܐp=ǟұN^ 2$W6_mI):dIl'%cP>"+X2M9 xHI8Pm;"CXI`=x5TwM̄yUN3Wl+lDs: *?h*{%WYS#+Mk!|Y?< o3s<"Qw!=qgO¬5iFܟ"q#퉮\q#[ܰ:8<MS ǻFy~.R% vwg<$pn*8=J;IR{ӞBuFҲߝxaom+9Nq8F"R:q9p$`N8Nw(n#d#DMody(ݰDC21 g9PKJQ *YV3WV+T$jy=)@|g^0 gZ uxʊ\98.ƒ֧}΋agyQ|>VW)ǕFɥa=ѣObu:=xŒB"SKX,v~a3Yl4@H c4ѓ-zfU=e!*p Ͼ3Ú? >ᶽ4YkK%Eqi}E]lZHoC<]k ?[1+C~4:X8)$8kCs z4O_vRFza&i |+ܥe 8l |m +U4akqސ)S~MBHwp^=6pLewL).grPA }~jdҺIzir;T.!*(?(}~UŽ-)3O dZjNSVHAsɟ'vзZ sA$Fr_N*cξṞjW;AӬ4Q2r"'*>c$wӢ-ڑn\.W\7 ,8:(㊧G]hoUlOgdl9yJzCuA2^ і6Kږ^esQ $*W"y;xӨ`ji9b8!ǹ]#)T#ʞL0uAsk i} ,5dV:r%jV\=ӟ!*'?+NmW$N]Y]_}C=sݣ^[;0c7g5\7-CCPXO#o0{HƠ w7#( /Ѣ=wo jd&;gL%; G>:fRJK.[s LJMen{ `qk{Xd !lm}-y*oZm/*$$ rz-^=ǛLuBJV>d`ͷI&{[$&3J l'm\d$;lnڃP--ۖ ?*S:$<'Amo2>fimw[+aA*`r3O/$15(N8zdYթ Ԇꯁ{"[u wg `V=~uI#ON<ed(.\[s-IK J|}եm]F4 ;uA1WŜ*wY+T}oٵϨ%{hD?ICP*O1YÍ y:9 :E͍fi G"Qن#hV m'5U`!dm~jZCk]ҭ (S_AIi?gRNbhz6ݲu9) yi B2HϺ-vU)TW%u_T}T|QR6Mɘ˙~h;TOqPQ++VQ2SCMփ9a)c-!YNLiK%Ğ)ZR \@}Q[t m\Y ! m| k0ז[ݕz%@ZH85fY2M; *gc3v_I$H]E\ۂ>l$}e24wTaw,;Ws}=4,%;A?i{!ĶCUgp3nԑ诬!CBQ靤/un-0\SeŸRu$Yq+*hIҺv5H*loKQ@WkPUR=c-rDM7& u9B۰J ½F*Sj#$K3_QNjݢ-KmW0* qõ:u;Vhp+K27c4)J>]?<א ְu#v{%LTK`ODE;xQQ "aoss$Υh۹W1)~UVG;pWp1[}UG@%GQAC'T+. ђy/;6`;|d6SI59ݏyk]ʱ?Q O:ǽn/JgO­(\aP7Bx&^OsGi89u9DSE0|{擂3:;ONҺ6p~) Iγ,gUuBVW/vĢBIBVpxP8OշH퉹ˣ̶rCH J'pdjP!9bZ hH[ϻi~km7M\7=0BJ!J9i *ŞF?Y}ux-Ev{i7nWKN u2JK*)'o4}H̲ԗv4uŁ\*)X[3ɨOꙖ/!CyER7 c)8ckkur2Ї0JTR@B6>ʞ#k3iO;Ւ9B|<:sn=$)pK"J{BW gU7wBYgc} )dLRgv&apC `dmǘ(}fe?.LJ@p=\uFv̕ia.5*Y8+W2% , sЀ<L H+5UT_Ҷx#CW_522@jn˅ғI#Bv5ǜwqH+yQd:HR{ 6rxy,6(fq{]Ie[|U2V3Rv%jWuMTIQqUzȈ f)H))}Mo'&^A mY)xxcqG&0LMvi&6b"#NmRPqy5huTMTTAu.ġ >/+>5dA \76qn$SnC4Q KCsk#)‥J)}IT~şPhUM>ɏgAߊ-D!(8 M2@190H⪸ .$2A&˺Ztcj{pu+y2a>mJu *u _MH+ \WYWg儥rT )*>NSTbldk6;lV}4.ԛ Qwb,Ñ.v:=ͩn(I(wO4A"Y՚oޡe:z3h#&K8; ovѩ,UmL/ H7Wc Y tGl/R0|ñ1X*Wv i}k3OUb27vky@t쉦~+J~ts>z닪+ wm)#>c4/5/iZ%Zݭ%%K[ =^SP٧GeykntT\%dT>xb\ÎC&cHhg6zPzRMc#Z-m-kCEOL\˾ QzB!ӄRI!ĩ8$$m`1@F jj' NIn}d6ON*G]wMF[\xNkHuIJx#YfCi% 㩣+CZ$,laa uSj=[cjdW3JFeŀ4;7jPbr\ۜOv\v@ V@u*TC 4ߚ٠;+ղڞ*uƙr)j"DžK1Bwt1*\vVz$7PIu++O#M_^Yg t,{<~zUd"*BˤKWUkwN[>q^iB@c<b^"-bۚ#kcu4 &쓵yn˻6hmmkKJUrz+KIVQmKX\,,_;m4 C~MѻMׅoL[n}*Z)\: ]mŝnA.Hl@;S'E_WXҍ[RL&KPa$$mI +PF7uod xF~4F%5['վkwh-pF!xݴϼtk F\)N8^JRò%H*qGN221CƁGԺ])HBX9|c?A#7ݼ= [L+s3^ݲhkFH^T *9PܒWaB K#sXI,)H)u^$c[ f>Yܘ%o:\ZVFg&2 S39:ՕN@ge&L@T[z Ssuyk+bz"6W.^FQ>@ BQ (jdՍF볷5aӐzQyWjȇ/պg:[5v{L(9,r?QFߦxNqDxV\[[Be)该ڏˠa}#"]n~faJ[s~H%>BWZ׹-3TzfEk}3-zmxI0a9!Dzc q@_pg4JqYN 䎤֌mPaDhIax,4w RH ^qYbFՔV2#^X.20M#c//utwu8wodqbmϪ#9dfmڪ4[Iq%H/2믗כ"#N9 P$q`|Bv$RҐNT'9<`毻:z%ECuo}tˢvF!Sk1"ىV_@agvo,m>TOXW>լKaK&ۥ P)U+%c`|ʿ3i/#A$sVGev\\܇.nY)r95s֛ٲ,_6߸O$:+umѥ->a)'2q _|Aq\"\Y. H>dNخ3SІF"m⥛΢@ w- (?ʣB1W=kfR qGȤsBmh"' BoxJ> :ՅAjPǝHe¥9&I"gbL\PFդ+o$,_քf nl8}zֳ5\ydn;V;ڕWo=KC/0@;qw]E.u 5.hC`mה2R @$ǺIe]pU=8ڿOH=sP6";pV?Mj\Vja(`D2k0eN6ྛk {+O,iD͡V1?qm',Œ |:$Ivgea&86PǏ%{s2_S1:{k)TR349Ț(ujP)O? *$KYJRvvX׳UْW2P/uy'&7MW<Γ}lh OBS^HV0 9=Y2*j2fRC>p0=zU 8<й1T&:\ @=Ͳ2C9huAu9⏬RFW[졘{q²?G4vp9#59HOG :YY›Oye8Z&3cz ra ݫ^*_ni1fiidTTzYh M7[TwUTO X RYjΥ p<~f)6Jk0[&׊":Ǧ?;ݙwwJ#* ~*`rFMLw;Y@XZ pHYA¶{BzvKψ%I(J%%]"?`Zi믹C%\xr9$q>UO%4kQ8YskguULT҅AICjlq#*a6`.:E{n4#^q;TԲ.Hm-Δ e⅍n8 (#p̚SMieڣ{ rH(# '834v V(t%Nj}Hh $tT2ve2vOθg-)J}lRw$'vрrO@ƥѧU|v8TbHXiyXJtEm ^}Ḇx%N2s7Ck]"L`)Q*L)CDܴߺ. IU}[]moMMF-w(ֵg#j@O*;/(yJPTcTZFƽ!m# 8$hp`xNzLArxӔgFbc{3p`"KaHxP$xBa:c= 4^cB[5侔IohF".i7>ѧ>]2[i#܄8>qV<vҦ{?|8ppuV6n'.ETE% eJ+ $TPh,RlXu?Jm#P-*PJܟƚ3!~8 Rq׎&[c3Z[7'ڑVTmI1^PKmIX } ;lJl-_ZI Ot<{7ْ3:8'柏2FAɨmP?MebHc؈SFqLgm]cH~CvϖxRiGӱnʐ >^ aش>|;09x5^e{UI?+$~|˟9T3@=' kn^S_ş-EN|x5洿h$p~LCIFR hFx1VbX,8ŀvl9IDRj>v"+CnpϥQ 访EOI=XF., yMkݙ89cdVO&ZfdkvR~w!տ=sgO­*? HnOs7iu ~ZrHi\ک V&(w!T$g/\9Gek+)ӟ׶pQT?SLrI۠pzK M(Y(庳m`*¸bx`cҩcx&ž?*Hʉ=2*3Z~eNJqicg(TA+"nj |gIPJ֑\08UnDuFu/6mQYPqi VOھ^|[ |F8-C-{Fp8޶Ɍe!7d)I2~ۂZhgx Qb+p"ؙ17%-jCq9Qy<׳$NB "2w̄WH@VBU9Ʈds%**]F,-o=k{:%jw]OiK)qB>CLv[S{d۹s8t%mK~l~1e׽*$'%C.f:g1jc%}ktB|)/v-*5iccҷ̸QV9YNc:,&;qPFyQE18 yup>ǖx8eP}{xAA}v>@89^3`=ٯ2R|YYQPwҏ|ʣLߧܼԆא0 MB 9=3XgE>(eN)!IRBJQ)Hܜ$t]Z.z}L,: PrPRq uT=# y,-Rqw}#wQo{?oV[Lm̈PuYĜ|gכNfX^[^ߎRH*Vsܞ{hZvQFSvj>Mڅ%$TFs^RBCCx&ȱW5lݜTe;$q$doY9mM ڽ*l{%P#yBk6qp?)T[8\-oiOL܅Ėq8ҷ$.k satĻ;Ōv!C'5Dc\ ڣZ|⋭ZnǶͲO0yث./$(wxfn~3{PRTRy))*_T `Нt"'MtQ{9E\T2:ͬ[eȟ B7%;R,r'n*qp\빧=s^yKNM/Qa=ER[aDWa:VV@=SHHR4G~4- S)ؔy|TS@HО ? vDp[}Q$F@O$rj{I GGZ99 iJ[$8amN(⃻$Œnx$c mW#eu& RpGimx}!9'^ZH*^kMEr +FkvƠO<%{)NOs4݌ưisWRD$zd Yaq 3s%0VmҖN|AG?=wz~@R9 zPHPQc%m&@|Q`uYw05ʮᵫ/Ą%]ғ'{x-rL[Ebq@IJG[Ns 1Awki R#©5#4C?3@&07=]=gpmeZ{jdɍ6a1=K* ̃ק%m MkɆ!I[T)Mfquq Yn]òkQ09R}UG=`e- p0d%w ' H G#\:Xh3ۿiUgZ㜆\䳸:VZr)X*ܧ} @OU7ݿIvzKN8⺬/@B'%6qxP衞AUًoa/Xew]dݏuz ;nCzy]Ֆbp1d)! OR@EmM@>68W\נ/wb-ݶBy$3r}<|\vsd[$n2RӁszFz_uF?pn͝GD p(z1W}hQlq-s8AP*ԩ'yW[[R$$$|acãk6?Tܰɨnu?*%'~#U=ݶ{RY!YP?@(tGÇisF3uk-dJm I[ a!=Hyta{%{džz3ҋP91(KHsyhVQ(Vhڂ'ȕRj|>JE]2O_*uGk *@$JeLU5axίE {1CǧU?6ǽn/UU'VmMQ\]Xz?]G'Fwdk_4;CQL#CRs0O$q20s V÷ēYkmB\pNlraqeb@RD QP@HO'qS`2Tb2 VTI'=Uw4KZ`ȋv;z[ݴٙ-.(:wu|b;5[4w-!ne m! 4k57D[؉ICml޵#:B#SOTsOi^Y*R:CqЁ#ݞrOZ[$My1q-x5s|c6Yp x M N\$&|VRHJNㄒFI*˱`{9N2^]{P[xRW2:pyZjl[N-6RWk-{EC; 9<'ӒjQh=>[ê!.%!%_72jLD+Scժ"|Msfݝ%NTN~|e@O;G%.ZwmF~Dȴ;fė=$ qv P>JJm WYϲ^TBNpBrGNj)*[TF0CU;N:^AeOЬz+GY[i]N,p> 98ooG{t=]kXJ A*BYhY Jڙm5:i#PQG˥MGYΠ\ZO<`VuNkGliI {&>u1"q}Vwۿem}U%iȬF'i8.)w:;^6U҄_$:>T}zWwUi4staNeRGɬj=eo0cݜZֵ͚mam\c]h뱶ST]׿_}ל[2=c:=(iZb}e 8 W<`Sn&$dm)9+H(⾬JFѴb*@$)A=|jϪ4|4>ްFi%T(𣷞jI{c*zpz@J\*f+S->ЗRj%>$I$Eh˽F{Ky T;:,U Q^.ޱXiE,~Ryl %2xbXinήia&;$:TI'i H@T}(xmu=U+ruVPt$LtJzқ*:p$# vs%?drP@+ W:E-odŽ#Rr8{z<uƳw埂S[L SCѱ}Yy*J%}RWmj~>ڿ* V-1@i̥$~JRTIm+)WUiHr)KDr޹RTHVŭqy>Y-mSb=3iu)Jp… Rv!眨IPB1*T$Z%,ւY$VѸHuMs/,SK겒XjjJJ*<yNs,\t)K␝*qD_-eek 1M~! 0r j=#(.qũF0 Bvc<|wU0;5{RS}мHkh6pީ8NxUR[duќ͵V8%e(#C^i3qG//hh`0o{okmkw+;AG>`=Av1̐Gvjr}V(~|{P|iQ IdahmGҟkQr{9m Do9P$ƙ+j  >@NpqCSӠ[I#Z'үT(G 6y^xe xJ*mi&^(Q_A)!78VPαeČԾR@V׊ loX 67N6U\!;JNќu;NqXZښ#key<_p[ 2w\x;E_/dP(W(!*I2cZ$@)Rܒ=׻3;L1an_ ASnsxls^ w<!CˑB@(mkBH$*HVO/Z%3uE 䝪=zy& *9uATT\gJl {%1gO"89+_츃X|de^ciAZe ee'#?Q O6ǽnUU&(\ڡu?]C'FwRhw@|֌qMWǻ2U!\hz}>h"rӚĦJ}-BnBi:Q`~sgʃ]ߌnְ.-IFVłA'92C\[raEg /rRRKK@j'H'b,5޳6'T+;.ZFk Nu,U|*sWn6/Gj paN6+ڬG z`Jd[-Օ̌( >,8>ʣ )F̈́ YL`qqdaXYͪDE3sVa(VGBR9O$ϣSly(Nyerv'h7{ȴi󝍌7|O'[޵tZb%;А#'nyM= ybG 뭸P;.lCOPКH@z[rvZ oHy$$@ygEh+ef&ܸkI+!@dZgKvn3LϽܵNH! y'Sm!|yd{G4 ժx%>6$:ve'#V5ద IH @]ӑ_r;ՓIO*> ǜrW}sĞuMGdx7 e嵽p5'?7n j߲ԄIn[Ium %IXyEHv'0ؐK:$FH>cDE]c.HaPԜ>vcT20:=>)$tRvUB]ehpݒ) (n||MT^)q 'c"w&|m Y%I#8T4%4յ `&IU,[f o:Zƅl0& e" RԓPSi}CJ\JaJ8>@(Wu [%\!wӓh[4UQ#Ɖ;N>3#Bdp7Yz l[nnۤ1ܯMx'1Wۢ9HX"8@#=h\#("HrS!;I<N@ <~5l ׏!u  F4u:샜$;KP%:Ղ*Xij_-٥IBHymMZ5.j})CdRG8_Zc!"`_b*5O5W{\KuKS|!%*ŀr~*f~dSqG 8SCyPQݤZbZYYRx֯ҶV#⸘27j⢚R꭫,F&'{7mXAcVbnnQ$2_-#=-3}ߘ\9A,v@.QM޽(MVkqI9+DGgg.jR13QqIH+JIQϙζ%EmƜJwAEChNzn2IlKqd)[Rs\:ThkX+V F<"bqe߲IM,Jϸ@jfFSKhi;^Qw+Zm%$dMpH Ir=Ʃ$[!,pʛZG$U?mxޛ!O] zp=|;8kp/B1NU܉-ve\ [.4o>@m|=3Y+w(簔ɐl HJFhVo_}@C7;/ _@#k5ӷ]JUI*lEpZ6>[M;s~^-NٟaB`+} zq 2Qn s䝨T(eIzP G+=8d*sH*(/)OZ&smsb AP\P.;fH I)ː9vw/BS\T넇X~JpJG@ :5eyKN[dZ0SKKM<\م+>yz:qG)iaX־ j;OCQDywu )P@O~7S֋44?:)ee$xME0w!ŕ6IQIWWBTIZCDfIp$f2U] Na'wnPC;z>6P^"$S! ?҇aq}XH\KROBzin( V{*vZxiRqKGad/L,GS}xɌQ#|zzq< )$pAEŦ|A}OP|,1ݿ۸ռ?6Es@̽\ N) kRNϨT#N^MCwHu* c)<+Yo2: A:+@)LL/ȏpf{kawђ2BR@8bm$1ѶFESѬZI&0_a_r^tJ(*I sC7t9Tr;vq @"U[5dץ)^cy 4>x>U4c@Jfn,|v}Y'9+dQ<_5v^Aexsewz?ٓVWa̚hnOrk~$]I'F[?$PO+{ 0RBy#TBApCj)>,`yPp8pYD9 L] ecNך,(CϸKJTGy (8 됟*KHW0U& 7_f\Й(̢"+\z fDvZTAZ S:}57oY gSVs{yѤĆ#%e% Cc*'jTzz AUENXdVzҴ!zӔv`G_Ji-cm1>é>qBC T(.":$#{*y6N:7G#֭::tlJP[^x~x<>J讐t^|ΚַWRZr YJ}īֽBЗɎMb0d/ >kmrIBnHmhJa덓qQض܊R,a//5gIjD8lF&:[eV R+Ħ}X>jX"P rMMĒCn#rEYip2-h&`RWĵùrP{ $ r*,)JN1D~HXA/WJB[iBI)Rr:D~+f~^KJWv䤑pSzcp󯈈zOfZ=TnHXܭ&21mW=!^B7N4%Cph W$qWMI $`c0i|[ J36*MFenR+$LvcQKenQ[ ͥG` դPOu_$0 ;1uQqㅱ˄E#n9*k3V[TDEF2vM[eƐVoMJw[%@(zס#4Hͥ@wX=Xwa~2]S51Ĺ*di?c$m e:b!ĸ'zKC>?@ݐ1CwvnRB3D7-.{QܑzS3%-!It%Hg MrWH~~ d]_H٫v{jv!JDe)nIRR;x UmIPjWq!ȝRTWkrpIP¬7\f;v[[a-&*ʊ%9u柵+Ep-^x{IS [}RWn$IF)17Ǒ> )`k[䧔HI?h8{U*S2d,GJ VV [-C@#&m/CEđ)H+#oY3w=A[d7m >ORZ#E+))چ TƋJ368N D9 Z{C.N#-8?2(=% vr<mE R09gʟZ]I.E)RIX)1U/~OQ3Ͽ%҈cGzt.$VExwQy} '裻v*=ۗSǓE $|늀hP>}[ )g8VQj\R Vi5ٷ{0[v ដWN>(QӼuR O9>X:A{ƪF(Zbo%Z ACyS} y%=Τi;ZI tHʏe$% I)*wP~/W aOgma|$NOZxJ~9_AQ1O9ǙDg:t ~m2ʀB8~Co42r'EE*gy^Z sD]HJx K{^ᖜ돛E0(d!M;*qҧ?ZB!הʼ.4=׷֏`Fl@Q%' tc6u8k/Lu\,Pexz5ݻci*^̈1 Tzz hyA[WԶNHIpp1O"G}kOe+M/pQZrNj!8T~꼮lӂצjIy9&-8VEk0[4ԧc* ))8@bL}LWJA >ʛRz:jGb[VR&շ*Am) qZr[ ?J= Rs! BI#·Cx'֣?J0Mʂ*euq#k\veFor2T$# <ڃ!6:P0Fsx矍xyoPHJJ$$>@Q(XNv(̶n22~`i$۽g~v<To4V B1Wy ܌qN/bSNy*NԀ}eC$mV *0AUǁT:a-~v\$5G38(I=8#C)W|5G9 &^Xp<} mwzl?ٓVU`̟ZѵMQVx%^7_5jm1eL.>K7`8I*dT6Br𰦕(01RSXQZtvH ;׿FNF}ث>gcJ"w۝RG֦4+ﶗޖ>;әpG&'۔Ab6wcXd)i'v 8zԽG,[uSu-!ea2 ;!iAO[a=וhk9}V:Equ[YԬv {S7G#f`;[L"ܢ N%[t+'FI5))Ãi#IɶGoV7.P;):.+!wI[~TѮ$y̚<S% m% +!$m}h-_5/ ?rՓWT4޳I:ݬFl Rг $ Byxvݢ}pz"P^kk@Q?>OG#m``xkC}V?=ﶹժ. Ɲe޵Kb,uW)pWFD^D2pwe {xyP:RܣTs uNGB^Z\%Oz$D\-e24Pӊ)#C(n&,Pu"EݘyɎ̆JKmcёB :t?/W#m4 ڪV,u޼%7f3)1,8Q8G;Hq F~vIxcﶗƋS ;ǾڎJ)RE_O%$>ڐ<+Ipkz+v?{,`+mElZGY2BDL P=?/L赒Uo$71_>V1 _?6^c/ա~_uCd' }c$pµiL%7AQQ{lš~_u<$g|åt>rms4Ί"xKyӋC#pZq*ҙh$d9%i*$ב@No8i~/1?/cG*!I}c)*A<ӯh܈$?-ﶽ{"{/1qXI<7hv[wSAz6ZJ!+֬zlΗ>;q [؄PJ4|;OdX4i0﶐hr!yﶦe,>e^JgH:}doq_{JRAԯp^pQ:lֿZDHmR" 2l%Ita9VF6 פ*O'k4B&Az| U)~_tY1ALM%vR+p< IơKn{:n1UBTA#n981m}BcmpTkU(7}nR/rٴ>wL)RZ{@' .sAi PsÕOx^C8(g>ڎJ)7$~x))'B8+iZ:kCNqmzV*I=ﶢ6^cR#uХpy5!'¶9i:@LU=OVi$\!SXr0?'VSc'*hJw$gKϕ XBpQhҖ$~m/('I$/K?60=)`zRI G͢JXBp~hJXBp}͢JXB{K}(Җ$?4RG+I$)=)`zRI }7?4Q^,JI!O~h{E=)$Q?0Qf,JI!/~e/2p=)`zRI }i}7p=)`zRI }7(Җ$Km̢JXB'N7}7=)mB{m` /=)mB{m`{m` ҖI$ _{M` ҖI$ vJ[G$w>_{MeJI ヲ0RoJI ߽0RomJI ߽0RomJI ߽0RkmJI Ͻ2>3=)mA{ ` 3=)mA{ `{ `MҖI$ _zemJI v2޻(hzRI}vJ[G$W޻+]0r{:3=)mQG 4*M*T_./resources/images/0000755000175000017500000000000013623376647012601 5ustar dandan./resources/images/no-cart.png0000664000175000017500000000606313623376647014661 0ustar dandanPNG  IHDR,MbKGD pHYs  tIME .;6EiTXtCommentCREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 75 t oIDATxb#A8 ޮݽW6GDe?,* @` , X@` ,X@`  X, @` ,X@`  X, @` |>~㱌ʞj%J} wXyXޟR4MT˥m[):Zf31 C!Qry\.2՞W j! nd2Q{|Y~z0swvp(=$Ix:ovv;Ƕlp8=wԭZFvUh+hgYu]\V=ig].HգVEj[EM`՗eYYQ6cYVj[[XtTgq,Y=Nkh:Jb& g^. Pv2 0 C|yyE݊j,˲T~,K @v u]aH[ }fj2~'Xm|~V.q=d PNn|ii!PmKjsteDZ}nE`z=*Ǎk 7hJ=˲,<7Bٶ-׶?0 f4XR]Y锺L&`K݊B]TjZjbض]tYԭ,4A߯BcL. v]W!o"ƼYɥ;eQ"DU0 [Xh(dVWg< MX,*/R0dWU MlNf4MeZ&Xh4M%Jz-7B]. ð%c7B]q\uQԳ,vl-ӉzɲL(k[Vr<y ,AV1 C0E`dYVXEm A`5n\7gGY׼|>W3wӉ7Be)\.K^a2ϥ( . UӨ{-EQ4ΒBe{I`p1X|>KbFEǒ)z0l\B%,K(^\, ׭޲nӋF`As"Q]*,TֵnE穉}iTYtv(2!,Q˥cV$ [+Xx<[ms!, DZr8y_k9Jה^84U8 6NF^'///yjG'I6A`U^Q2U֭LӔt_oZsֺUYXﻮ+H9\.E, ~|up8u3ږ6lL`4U[rG&_as\.F`,ZGt!iv inu cke _yx"˲Ȳ,uz<_ft:htjŧ@HHIpzߟE׋n**MvcUw]vbjlV4  ^-Mx}dYdplg#X:1p88iJOEi>|p" >4u" aX5,,b8հ\. q@ *A *cݚL*XP IeUxQݎVfikm & |[eq{{[oá!X%]z^eW@fyc,/Y!V^ |]eqssSXu+1K3o ^43̻,},,?n…,xv:bXf)MjZ\]]Y]xHӴT5N ݕd,lVm߯n@fې۷Vc,xlb>YVX,Jd͂>~X{_3Lb4@ϱZ,&6PJ$@븻+UzLS/abKGD pHYs.#.#x?vtIME : hdB_IDATxyxUV: Y {Xȸ##0ʋیqAQQtuAQA@qP|ǑUM$,fIwꪺGW&)'~nS"|lyz&I9|@YYƮ]X%J/<)IQ]~-^21] ]W>ՁPE!lU/$0K&RgYYHw9' pl(lI &s,ەii} X($1轻_Rmʩ"&mm=$#An1wda% @N 'O$JPBKsXh_h8vrŖ2dI݇oZ `+h{= "৲:T9{tgߍFڷL'r^+#J*91{̻ Vʛ?TգT30z@6(5A2Biҟߓ5y sm$m4{V2: #wh'Fv!^%Aj{by-pc @1Mjd(?,e~4|ڽ[~*_/ MLΚ%Y%ouHoH)lr\^Z\Psp}p⦲o͍0fMQ+?<f4ʘWp @`Ͳ R`+KK`(巫c_"sƠ3U U$i`1Mb[:M}4=3gOK 8GI!L{~d۷SVɊQ?? u9nյ% }xՉ?; jAW+g4FqI 8A"#8T1FtHP8tP5A]CPӡU ݇shL@%IxmCbBrWÿ:5E [NvgY]/]V)KyU$F+ xPXyE5Fg"ZWjk%q):ӑ־)"wfM4Hh&ECњhMj L2A=`1 &dּ98%k?Ho xj)֕OWIUa,Rr_M @2Abh[SȜqzL22N;SB9P_EuU0ȈQ?RIEiuU׸8#sr10 dM:޷Ej^dhTUM9i(k /-<__lY7ۦy=;ťTWq'+w}35Czâ] ڥwۇ}/˖Nd HVُ0K|{p2,r%8[2ңB$**РW+I2A" =1oLy5;к@sY{pw+^]Pu1lp4b-!.)m;= p~L\BKOإ>}4[.LLu$K]F3=4Z Ͻ (d#jAM)\5;pp7,ۧzMӡMc pPJ `of~k%HNDFBotsZG*1Y#&q§p%GKbl a]umurP&(zڂzM)PZ]G`Gp҈3*ϱU+Z]j \gt~kUE5zj :c} Hh%. BDNrn=QGIo]xh۷r:/Z[)4T!P掆"x.);UZ2<-]5Uj4֞Bo?ua9[ P|E}9W cDOQQYσ?׍>6f,?Qr AWW6KW۰zK+tl6Iao<.IW[׈@Rf14|!@4FBH!hDι%nƒΏ' ~<:9'\i7.K @*wAa(n8rmwԿ,#^+}owЊSF~! ՈhX\3'%CF@OԬovЉ$Qn/^5vS;Fwڗr*_~-33-[\ 9ox.qlӧo,..&8]u8gXKznRFTp>Nr*S`,ӏUJ!Yl@IC’9L3gj01:ӡk:Ԡ3HZse{ѫUts)%n,@6WWP[D*wd?vU8?fIUUDE,lR £,˭vl^1z ٸq]۶mdǻo~_~~QcW9C\{ӭiة(AS-5D#֚ 𸽨Q6+,IH͈-A]>: ak4#yJB{oy2c4*w{nn~31fdeeӗ̜9%"s)]nݵ֭%KJt^0.:F+M6ma&amrǍ1b$Inݺ~ڢwd^.W{f*R#*"HO>KW]uU#[m6[ɓ'IK7n=zW圗`!Ǫ#OHIIa.'|Xqs',akVG*رc$EvFll,WU5wf,(/@ n7;;{o]]%RYii+V0duVOXH/7XlEz iҥfEMн{I&p~ _{o6v999ݻ*¤)1#V:t Ǔ6{ر+|d~*Yu^d>L^zOqqc @!Cfjtn/4& +1ȃ>~%77Hv;dw=zv<9.\ @1/n? sڣGς y w#>>~a#eXHEE[ou+!9\[Ӵ[n5\... oΜ9o.^8}UQQQcϟ?瞻_~oq_.+Tdȑ7Xw>{y{SSS;9rƍG]dɒq/dff~X-[l @AM&^{o˩jkkOe_J6m4?"??l_CRRҟ>ecƌy" l)c .\p¤I21qg'LPPPf\KC tԩm`7eYgر=W_ܹs2eʳC )h^˖- }.**".- W05\^H555.SRcݤ(3tn}GAEE)))O:UcrD*@JJ z,..& ftvAp>eE~,CRRXo=ř(%N3#2J6܅$ˍ$RH .(p:jR4b:tdEQR 9v;f͚0clڵkgee-[`qιp^t鄕+Wf/_|ά1=}VZ55&&&_ԨQ_s\rv=;xz}.-TUb1[Gal0Fc&#|,\.Wtv9V@.Hs0{lɓ[ɷDp$3jT9=}sN]wu>rypMnذ8!k׮z<1x]L(J RDvzt $IƏ9sWrRNshj„ ߖRJ{SLI6ΫȭYs,W>~@zX6tC- "2lذ#- 0X}# |p{zށ5?daa޽{_y|c xԴ%%%_r·*ҼO>6tЁ۶m@ܹs#TWh(aɘ1 +1%~]ԈR/Zhbs0l:^Nz饗 s^#˲sjq8p8Ofs}0sڵlٲeDzGpB{zz{s9y7j,?r6|{Nz)4E8qݻX|'|(99#M[n\.v֍Ϙ1=Ih{w8 \(On)ŋ&,hѢG}oN<)-KY~}q>L^J7@v 5(Z}'^z\N?zP]]P!\=ؼ3g~~7 8KQJ0x|pѐ!C.;v|@^6E`WEIjjH+٭[7,_VJ5Ҋc~c2iӦ  '/d꺮ݷo~,Ko)***҆s.TY|;vl_9?fY:w9hРgTUxGVTTsטBH6mCif/++ݾ}ZJfiKA3H^L[0rWϵ e2`lq-^,yR;Ji c#,2ynPmWjzer 4S4ӕTkg"2L3@v 9w!@ @ @ @ @ @ @ @ @ @ \){go/IENDB`./resources/images/cen64-qt.png0000664000175000017500000001572213623376647014661 0ustar dandanPNG  IHDR>abKGD pHYs.#.#x?vtIME : hdB_IDATxyxUV: Y {Xȸ##0ʋیqAQQtuAQA@qP|ǑUM$,fIwꪺGW&)'~nS"|lyz&I9|@YYƮ]X%J/<)IQ]~-^21] ]W>ՁPE!lU/$0K&RgYYHw9' pl(lI &s,ەii} X($1轻_Rmʩ"&mm=$#An1wda% @N 'O$JPBKsXh_h8vrŖ2dI݇oZ `+h{= "৲:T9{tgߍFڷL'r^+#J*91{̻ Vʛ?TգT30z@6(5A2Biҟߓ5y sm$m4{V2: #wh'Fv!^%Aj{by-pc @1Mjd(?,e~4|ڽ[~*_/ MLΚ%Y%ouHoH)lr\^Z\Psp}p⦲o͍0fMQ+?<f4ʘWp @`Ͳ R`+KK`(巫c_"sƠ3U U$i`1Mb[:M}4=3gOK 8GI!L{~d۷SVɊQ?? u9nյ% }xՉ?; jAW+g4FqI 8A"#8T1FtHP8tP5A]CPӡU ݇shL@%IxmCbBrWÿ:5E [NvgY]/]V)KyU$F+ xPXyE5Fg"ZWjk%q):ӑ־)"wfM4Hh&ECњhMj L2A=`1 &dּ98%k?Ho xj)֕OWIUa,Rr_M @2Abh[SȜqzL22N;SB9P_EuU0ȈQ?RIEiuU׸8#sr10 dM:޷Ej^dhTUM9i(k /-<__lY7ۦy=;ťTWq'+w}35Czâ] ڥwۇ}/˖Nd HVُ0K|{p2,r%8[2ңB$**РW+I2A" =1oLy5;к@sY{pw+^]Pu1lp4b-!.)m;= p~L\BKOإ>}4[.LLu$K]F3=4Z Ͻ (d#jAM)\5;pp7,ۧzMӡMc pPJ `of~k%HNDFBotsZG*1Y#&q§p%GKbl a]umurP&(zڂzM)PZ]G`Gp҈3*ϱU+Z]j \gt~kUE5zj :c} Hh%. BDNrn=QGIo]xh۷r:/Z[)4T!P掆"x.);UZ2<-]5Uj4֞Bo?ua9[ P|E}9W cDOQQYσ?׍>6f,?Qr AWW6KW۰zK+tl6Iao<.IW[׈@Rf14|!@4FBH!hDι%nƒΏ' ~<:9'\i7.K @*wAa(n8rmwԿ,#^+}owЊSF~! ՈhX\3'%CF@OԬovЉ$Qn/^5vS;Fwڗr*_~-33-[\ 9ox.qlӧo,..&8]u8gXKznRFTp>Nr*S`,ӏUJ!Yl@IC’9L3gj01:ӡk:Ԡ3HZse{ѫUts)%n,@6WWP[D*wd?vU8?fIUUDE,lR £,˭vl^1z ٸq]۶mdǻo~_~~QcW9C\{ӭiة(AS-5D#֚ 𸽨Q6+,IH͈-A]>: ak4#yJB{oy2c4*w{nn~31fdeeӗ̜9%"s)]nݵ֭%KJt^0.:F+M6ma&amrǍ1b$Inݺ~ڢwd^.W{f*R#*"HO>KW]uU#[m6[ɓ'IK7n=zW圗`!Ǫ#OHIIa.'|Xqs',akVG*رc$EvFll,WU5wf,(/@ n7;;{o]]%RYii+V0duVOXH/7XlEz iҥfEMн{I&p~ _{o6v999ݻ*¤)1#V:t Ǔ6{ر+|d~*Yu^d>L^zOqqc @!Cfjtn/4& +1ȃ>~%77Hv;dw=zv<9.\ @1/n? sڣGς y w#>>~a#eXHEE[ou+!9\[Ӵ[n5\... oΜ9o.^8}UQQQcϟ?瞻_~oq_.+Tdȑ7Xw>{y{SSS;9rƍG]dɒq/dff~X-[l @AM&^{o˩jkkOe_J6m4?"??l_CRRҟ>ecƌy" l)c .\p¤I21qg'LPPPf\KC tԩm`7eYgر=W_ܹs2eʳC )h^˖- }.**".- W05\^H555.SRcݤ(3tn}GAEE)))O:UcrD*@JJ z,..& ftvAp>eE~,CRRXo=ř(%N3#2J6܅$ˍ$RH .(p:jR4b:tdEQR 9v;f͚0clڵkgee-[`qιp^t鄕+Wf/_|ά1=}VZ55&&&_ԨQ_s\rv=;xz}.-TUb1[Gal0Fc&#|,\.Wtv9V@.Hs0{lɓ[ɷDp$3jT9=}sN]wu>rypMnذ8!k׮z<1x]L(J RDvzt $IƏ9sWrRNshj„ ߖRJ{SLI6ΫȭYs,W>~@zX6tC- "2lذ#- 0X}# |p{zށ5?daa޽{_y|c xԴ%%%_r·*ҼO>6tЁ۶m@ܹs#TWh(aɘ1 +1%~]ԈR/Zhbs0l:^Nz饗 s^#˲sjq8p8Ofs}0sڵlٲeDzGpB{zz{s9y7j,?r6|{Nz)4E8qݻX|'|(99#M[n\.v֍Ϙ1=Ih{w8 \(On)ŋ&,hѢG}oN<)-KY~}q>L^J7@v 5(Z}'^z\N?zP]]P!\=ؼ3g~~7 8KQJ0x|pѐ!C.;v|@^6E`WEIjjH+٭[7,_VJ5Ҋc~c2iӦ  '/d꺮ݷo~,Ko)***҆s.TY|;vl_9?fY:w9hРgTUxGVTTsטBH6mCif/++ݾ}ZJfiKA3H^L[0rWϵ e2`lq-^,yR;Ji c#,2ynPmWjzer 4S4ӕTkg"2L3@v 9w!@ @ @ @ @ @ @ @ @ @ \){go/IENDB`./resources/cen64qt.qrc0000664000175000017500000000051513623376647013332 0ustar dandan images/cen64.png other/LICENSE images/not-found.png images/no-cart.png other/VERSION locale/cen64-qt_fr.qm locale/cen64-qt_ru.qm ./resources/locale/0000755000175000017500000000000013623376647012573 5ustar dandan./resources/locale/cen64-qt_ru.qm0000664000175000017500000005350613623376647015214 0ustar dandanZm`'Zy%?Z?[5FY]C.uQ.~ ;l>蹥M h*D]Ký3&$-1D?LJRZphWa[ &Eٖ X{IQͬ0sU0p7(D!6 &FBmOŎ* ).%_jA@[HZ~nM [y/0Ezq$I3I7II)+/ 3}2  UCt(ĘP)&[.`G>;4V|5h6%lq69rw:-|%7~}j=tU"HG#} }F9eY9%]PJÊ@QgE;[3gC`C8& cS( $G7IN  >WmۄL|J'Z"tU! GZ2h6&Ѫ&()5"Iz website AboutDialogv0AB@08205<K9 8=B5@D59A 4;O <ParentName>, =0?8A0==K9 =0 Qt.2A customizable launcher for using Qt. AboutDialog* ?@>3@0<<5 <AppName>About  AboutDialog$ 5?>78B>@89 GitHubGitHub repository AboutDialog 5@A8OVersion AboutDialog5B 48A:0No DiskDDView B<5=0CancelDownloadDialog$09;FileDownloadDialog4URL 83@K =0 thegamesdb.netFrom thegamesdb.net URL of gameDownloadDialog0720=85 83@K: Name of Game:DownloadDialog 09B8SearchDownloadDialog009B8 8=D>@<0F8N >1 83@5Search Game InformationDownloadDialog.8;8 845=B8D8:0B>@ 83@K: or Game ID:DownloadDialog0$09; 64DD IPL =5 =0945=.64DD IPL file not found.EmulatorHandler0$09; 64DD ROM =5 =0945=.64DD ROM file not found.EmulatorHandler"64DD =5 2:;NG5=>.64DD not enabled.EmulatorHandlerPA?>;=O5<K9 D09; <ParentName> =5 =0945=." executable not found.EmulatorHandler<ParentName> =5>6840==> ?@5:@0B8; @01>BC. @>A<>B@8B5 6C@=0; 4;O ?>;CG5=8O 1>;55 ?>4@>1=>9 8=D>@<0F88.C quit unexpectedly. Check the log for more information.EmulatorHandler"-<C;OF8O 70?CI5=0Emulation startedEmulatorHandler(-<C;OF8O >AB0=>2;5=0Emulation stoppedEmulatorHandlerP$09; ROM =5 2K1@0= 8;8 64DD =5 2:;NG5=>.$No ROM selected or 64DD not enabled.EmulatorHandler&$09; ROM =5 2K1@0=.No ROM selected.EmulatorHandler.5:>@@5:B=K9 D09; 64DD.Not a valid 64DD File.EmulatorHandler,5:>@@5:B=K9 D09; Z64.Not a valid Z64 File.EmulatorHandler.$09; PIF IPL =5 =0945=.PIF IPL file not found.EmulatorHandler&$09; ROM =5 =0945=.ROM file not found.EmulatorHandler*@>A<>B@5BL 6C@=0;... View Log...EmulatorHandler=8<0=85WarningEmulatorHandler5B :0@B@8460No CartGridView5B :0@B@8460No CartListView&C@=0; <ParentName> Log LogDialog0:@KBLClose LogDialog& ?@>3@0<<5...&About MainWindow&0@0<5B@K... &Configure... MainWindow4&>=25@B8@>20BL V64/n64...&Convert V64/n64... MainWindowB&03@C78BL/>1=>28BL 8=D>@<0F8N...&Download/Update Info... MainWindow&-<C;OF8O &Emulation MainWindow &$09;&File MainWindow&> 25AL M:@0= &Full-screen MainWindow&><>IL&Help MainWindow& 0A?>;>65=85&Layout MainWindow&B:@KBL ROM... &Open ROM... MainWindow &K9B8&Quit MainWindow &1=>28BL A?8A>: &Refresh List MainWindow&0AB@>9:8 &Settings MainWindow&0?CAB8BL&Start MainWindow"&!B@>:0 A>AB>O=8O &Status Bar MainWindow&84&View MainWindowd>102LB5 :0B0;>3, A>45@60I89 D09;K ROM, A ?><>ILN &Add a directory containing ROMs under  MainWindowA5 D09;K All Files MainWindow B<5=0Cancel MainWindow<&#40;8BL B5:CICN 8=D>@<0F8N...D&elete Current Info... MainWindow:;NG8BL 64DD Enable 64DD MainWindow !5B:0 Grid View MainWindow0?CAB8BLLaunch MainWindow !?8A>: List View MainWindow,5B 2K2>40 @57C;LB0B>2 No Output MainWindow5B D09;>2 ROMNo ROMs MainWindowB5 =0945=K D09;K ROM 2 D09;5 ZIP.No ROMs found in ZIP file. MainWindow5BNone MainWindow B:@KBL D09; ROM Open ROM File MainWindowK1@0BL ROM Select ROM MainWindow\0AB@>9:8->0@0<5B@K->CB8, GB>1K 8E ?>:070BL.,Settings->Configure->Paths to use this view. MainWindow&AB0=>28BLSt&op MainWindow"01;8F0 Table View MainWindowC@=0; >BACBAB2C5B. <ParentName> =5 1K; 70?CI5= 8;8 =5 2K2>48; 8=D>@<0F8N A <><5=B0 ?>A;54=53> 70?CA:0.^There is no log. Either has not yet run or there was no output from the last run. MainWindow*@>A<>B@5BL 6C@=0;... View Log... MainWindow %1 %1 MBQObjectCRC1CRC1QObjectCRC2CRC2QObject 07@01>BG8: DeveloperQObject&03@C7:0 7025@H5=0!Download Complete!QObjectESRBESRBQObject<O D09;0FilenameQObject,<O D09;0 (@0AH8@5=85)Filename (extension)QObject1;>6:0 83@K Game CoverQObject603@C7:0 8=D>@<0F88 >1 83@5Game Information DownloadQObject0720=85 83@K Game TitleQObject0=@GenreQObjectGoodNameGoodNameQObject=CB@5==55 8<O Internal NameQObjectMD5MD5QObject.>;LH5 @57C;LB0B>2 =5B.No more results found.QObject  57C;LB0B>2 =5B.No results found.QObject5 =0945=> Not foundQObject 17>@OverviewQObject 3@>:8PlayersQObject740B5;L PublisherQObject 59B8=3RatingQObject0B0 2K?CA:0 Release DateQObject."@51C5BAO D09; :0B0;>30Requires catalog fileQObject RumbleRumbleQObject"8? A>E@0=5=89 Save TypeQObject  07<5@SizeQObject58725AB=K9 ROM Unknown ROMQObject$09; ZipZip FileQObject B<5=0Cancel RomCollection5 C40;>AL ?>4:;NG8BLAO : 1075 40==KE SQLite. 5:>B>@K5 DC=:F88 ?@>3@0<<K <>3CB =5 @01>B0BL.@Could not connect to Sqlite database. Application may misbehave. RomCollection0070 40==KE =5 703@C65=0Database Not Loaded RomCollection,03@C7:0 D09;>2 ROM...Loading ROMs... RomCollection.$09;K ROM =5 =0945=K 2 No ROMs found in  RomCollection*$09;K ROM =5 =0945=K.No ROMs found. RomCollection=8<0=85Warning RomCollection......SettingsDialog($09; EEPROM 16 :18B:16 kbit EEPROM file:SettingsDialog$$09; 16kbit EEPROM16kbit EEPROM FileSettingsDialog$$09; EEPROM 4 :18B4 kbit EEPROM file:SettingsDialog"$09; 4kbit EEPROM4kbit EEPROM FileSettingsDialog"$09; 64DD IPL ROM64DD IPL ROM FileSettingsDialog$09; 64DD IPL:64DD IPL file:SettingsDialog:A?>;=O5<K9 D09; <ParentName> ExecutableSettingsDialog<b>@8<5G0=85:</b> /7K: 87<5=8BAO B>;L:> ?>A;5 ?5@570?CA:0 ?@>3@0<<K.LNote: Language changes will not take place until application restart.SettingsDialog:A5AAC0@: Accessory:SettingsDialog>1028BL...Add...SettingsDialog2>?>;=8B5;L=K5 =0AB@>9:8:Additional Parameters:SettingsDialog2K@02=820BL 02B><0B8G5A:8Adjust AutomaticallySettingsDialog$>ABC?=K5 AB>;1FK:Available Columns:SettingsDialog&>ABC?=K5 M;5<5=BK:Available Items:SettingsDialog&$>=>2>5 87>1@065=85Background ImageSettingsDialog$>=: Background:SettingsDialog '5@=K9BlackSettingsDialog !8=89BlueSettingsDialog>@8G=52K9BrownSettingsDialog17>@... Browse...SettingsDialog$09;K CEN64 CEN64 FilesSettingsDialog.A?>;=O5<K9 D09; CEN64:CEN64 executable: SettingsDialogCEN64-Qt 1C45B 8A:0BL 2A5 D09;K Z64, V64, N64, NDD 8 ZIP 2 MB8E :0B0;>30ERCEN64-Qt will search for all .z64, .n64, .ndd, and .zip files in these directoriesSettingsDialog B<5=0CancelSettingsDialog$09; :0B0;>30 Catalog FileSettingsDialog!B>;1FK:Columns:SettingsDialog>=B@>;;5@ #1 Controller #1SettingsDialog>=B@>;;5@ #2 Controller #2SettingsDialog>=B@>;;5@ #3 Controller #3SettingsDialog>=B@>;;5@ #4 Controller #4SettingsDialogController PakController PakSettingsDialog&$09; Controller PakController Pak FileSettingsDialog($09; Controller Pak:Controller Pak File:SettingsDialog>=B@>;;5@K ControllersSettingsDialog "5:CI85 AB>;1FK:Current Columns:SettingsDialog""5:CI85 M;5<5=BK:Current Items:SettingsDialog*>;L7>20B5;LA:89 D>=:Custom Background:SettingsDialog>;C1>9CyanSettingsDialog "5<=K9DarkSettingsDialog"5<=>-A5@K9 Dark GraySettingsDialog> C1K20=8N DescendingSettingsDialog<B>1@060BL 87>1@065=85 >1;>6:8Display Cover ImageSettingsDialog\03@C78BL 8=D>@<0F8N >1 83@5 (thegamesdb.net):+Download Game Information (thegamesdb.net):SettingsDialog-<C;OF8O EmulationSettingsDialog:;NG5=>:Enabled:SettingsDialogG5=L 1>;LH>9 Extra LargeSettingsDialogG5=L <0;5=L:89 Extra SmallSettingsDialogF5@2K9 M;5<5=B 2 :0G5AB25 703>;>2:0First Item as HeaderSettingsDialog$09; FlashRAM FlashRAM FileSettingsDialog$09; FlashRAM:FlashRAM file:SettingsDialog5;5=K9GreenSettingsDialog !5B:0GridSettingsDialog& 07<5@ 87>1@065=8O: Image Size:SettingsDialog7>1@065=85:Image:SettingsDialog&25B <5B:8: Label Color:SettingsDialog"5:AB <5B:8: Label Text:SettingsDialog /7K:: Language:SettingsDialog>;LH>9LargeSettingsDialog!25B;K9LightSettingsDialog!25B;>-A5@K9 Light GraySettingsDialog !?8A>:ListSettingsDialog!@54=89MediumSettingsDialogB:;NG8BL 72C:: No audio:SettingsDialog B:;NG8BL 2845>: No video:SettingsDialog5BNoneSettingsDialog1KG=K9NormalSettingsDialogOKOKSettingsDialog@0=652K9OrangeSettingsDialog @C3>5OtherSettingsDialog K2>4 2 :>=A>;L:Output to Console:SettingsDialog $09; PIF IPL ROMPIF IPL ROM FileSettingsDialog$09; PIF IPL: PIF IPL file:SettingsDialogCB8PathsSettingsDialog >7>2K9PinkSettingsDialogC@?C@=K9PurpleSettingsDialog&0B0;>38 D09;>2 ROMROM DirectoriesSettingsDialog$0B0;>3 D09;>2 ROM ROM DirectorySettingsDialog7>1@065=85 ROM ROM ImageSettingsDialog5B:0 ROM ROM LabelSettingsDialog&0B0;>3 D09;>2 ROM: ROM catalog:SettingsDialog@0A=K9RedSettingsDialog#40;8BLRemoveSettingsDialog."@51C5BAO D09; :0B0;>30Requires catalog fileSettingsDialogRumble Pak Rumble PakSettingsDialogT=>3>?>B>G=K9 70?CA: (A=865==0O B>G=>ABL):#Run multithreaded (quasi-accurate):SettingsDialog$09; SRAM SRAM FileSettingsDialog$09; SRAM: SRAM file:SettingsDialog$0B0;>3 A>E@0=5=89Save DirectorySettingsDialog!>E@0=8BL D09;K Save FilesSettingsDialog&0B0;>3 A>E@0=5=89:Save directory:SettingsDialog0AB@>9:8SettingsSettingsDialog(&25B 0:B82=>9 B5=8: Shadow Active Color:SettingsDialog*&25B =50:B82=>9 B5=8:Shadow Inactive Color:SettingsDialog>:070BL <5B:C: Show Label:SettingsDialog0;5=L:89SmallSettingsDialog!>@B8@>2:0:Sorting:SettingsDialogX#:070BL 8=48284C0;L=K5 D09;K 4;O A>E@0=5=89:"Specify individual files for savesSettingsDialog4 0ABO3820BL ?5@2K9 AB>;15FStretch First ColumnSettingsDialog !C?5@SuperSettingsDialog"01;8F0TableSettingsDialog 07<5@ B5:AB0: Text Size:SettingsDialog "5<0:Theme:SettingsDialogTransfer Pak Transfer PakSettingsDialog*$09; ROM Transfer PakTransfer Pak ROM FileSettingsDialog,$09; ROM Transfer Pak:Transfer Pak ROM:SettingsDialog8$09; A>E@0=5=8O Transfer PakTransfer Pak Save FileSettingsDialog:$09; A>E@0=5=8O Transfer Pak:Transfer Pak Save:SettingsDialogtA?>;L7C9B5 4;O ?5@540G8 4>?>;=8B5;L=KE ?0@0<5B@>2 2 CEN64*Use to pass additional parameters to CEN64SettingsDialog 5;K9WhiteSettingsDialog 5;BK9YellowSettingsDialog5B :0@B@8460No Cart TableView<b>@8<5G0=85:</b> A;8 2K C40;O5B5 8=D>@<0F8N >1 MB>9 83@5, ?>A:>;L:C 55 RNOTE: If you are deleting this game's information because the game doesn't TheGamesDBScraper@@>4>;68BL ?>;CG5=85 8=D>@<0F88?Continue scraping information?TheGamesDBScraper4#40;8BL 8=D>@<0F8N >1 83@5Delete Game InformationTheGamesDBScraper<#40;8BL B5:CICN 8=D>@<0F8N 4;O"Delete the current information forTheGamesDBScraperA5 25@=>?Does this look correct?TheGamesDBScraperH81:0 A5B8 Network ErrorTheGamesDBScraperK?CI5=> =0: Released on: TheGamesDBScraper`@5<O 70?@>A0 8AB5:;>. @>25@LB5 =0AB@>9:8 A5B8./Request timed out. Check your network settings.TheGamesDBScraper`> 2@5<O 703@C7:8 A TheGamesDB ?@>87>H;0 >H81:0:>The following error from TheGamesDB occured while downloading:TheGamesDBScraper~<AppName> =5 1C45B >1=>2;OBL 8=D>@<0F8N 4;O MB>9 83@K, ?>:0 2K PThis will cause to not update the information for this game until you TheGamesDBScraperd8 4>1028BL 83@C. -B> ?><>65B 4@C38< ?>;L7>20B5;O<.4and add the game so other users can benefit as well.TheGamesDBScraper0;CGH5 A>740BL 0::0C=B =0better to create an account onTheGamesDBScraper~=5B =0 TheGamesDB 8 <AppName> 2K405B 8=D>@<0F8N > 4@C3>9 83@5, Rexist on TheGamesDB and pulled the information for different game, it's TheGamesDBScraper\=5 2K?>;=8B5 "03@C7:0/1=>28BL 8=D>@<0F8N..."'force it with "Download/Update Info..."TheGamesDBScraper&>=25@B5@ <AppName> Converter V64ConverterA5 D09;K All Files V64Converter4>=25@B8@>20=85 7025@H5=>!Conversion complete! V64Converter(B:@KBL D09; v64/n64Open v64/n64 File V64Converter$!>E@0=8BL D09; Z64 Save z64 File V64Converter"$09;K ROM V64/n64 V64/n64 ROMs V64Converter$09;K ROM Z64Z64 ROMs V64Converter$C65 2 D>@<0B5 Z64!already in z64 format! V64ConverterT=5 O2;O5BAO :>@@5:B=K< D09;>< V64 8;8 N64!!is not a valid .v64 or .n64 file! V64Converter ) , ./resources/locale/cen64-qt_ru.ts0000664000175000017500000016007513623376647015225 0ustar dandan AboutDialog About <AppName> О программе <AppName> Version Версия A customizable launcher for <ParentName> using Qt. Настраиваемый интерфейс для <ParentName>, написанный на Qt. <ParentName> website Сайт <ParentName> GitHub repository Репозиторий GitHub DDView No Disk Нет диска DownloadDialog Search Game Information Найти информацию об игре File Файл Name of Game: Название игры: or Game ID: или идентификатор игры: From thegamesdb.net URL of game URL игры на thegamesdb.net Search Найти Cancel Отмена EmulatorHandler Warning Внимание <ParentName> quit unexpectedly. Check the log for more information. <ParentName> неожиданно прекратил работу. Просмотрите журнал для получения более подробной информации. View Log... Просмотреть журнал... Emulation stopped Эмуляция остановлена <ParentName> executable not found. Исполняемый файл <ParentName> не найден. PIF IPL file not found. Файл PIF IPL не найден. 64DD IPL file not found. Файл 64DD IPL не найден. 64DD ROM file not found. Файл 64DD ROM не найден. No ROM selected. Файл ROM не выбран. 64DD not enabled. 64DD не включено. Not a valid Z64 File. Некорректный файл Z64. Not a valid 64DD File. Некорректный файл 64DD. No ROM selected or 64DD not enabled. Файл ROM не выбран или 64DD не включено. Emulation started Эмуляция запущена ROM file not found. Файл ROM не найден. GridView No Cart Нет картриджа ListView No Cart Нет картриджа LogDialog <ParentName> Log Журнал <ParentName> Close Закрыть MainWindow &File &Файл &Open ROM... &Открыть ROM... &Convert V64/n64... &Конвертировать V64/n64... &Refresh List О&бновить список &Download/Update Info... &Загрузить/обновить информацию... D&elete Current Info... &Удалить текущую информацию... &Quit &Выйти &Emulation &Эмуляция &Start &Запустить St&op &Остановить Enable 64DD Включить 64DD View Log... Просмотреть журнал... &Settings &Настройки &Configure... &Параметры... There is no log. Either <ParentName> has not yet run or there was no output from the last run. Журнал отсутствует. <ParentName> не был запущен или не выводил информацию с момента последнего запуска. &Layout &Расположение &View &Вид None Нет Table View Таблица Grid View Сетка List View Список &Full-screen В&о весь экран &Status Bar &Строка состояния &Help &Помощь &About &О программе... Add a directory containing ROMs under Добавьте каталог, содержащий файлы ROM, с помощью Settings->Configure->Paths to use this view. Настройки->Параметры->Пути, чтобы их показать. No Output Нет вывода результатов All Files Все файлы Open ROM File Открыть файл ROM No ROMs Нет файлов ROM No ROMs found in ZIP file. Не найдены файлы ROM в файле ZIP. Select ROM Выбрать ROM Launch Запустить Cancel Отмена QObject GoodName GoodName Filename Имя файла Filename (extension) Имя файла (расширение) Zip File Файл Zip Internal Name Внутреннее имя Size Размер MD5 MD5 CRC1 CRC1 CRC2 CRC2 Players Игроки Rumble Rumble Save Type Тип сохранений Game Title Название игры Release Date Дата выпуска Overview Обзор ESRB ESRB Genre Жанр Publisher Издатель Developer Разработчик Rating Рейтинг Game Cover Обложка игры Unknown ROM Неизвестный ROM Requires catalog file Требуется файл каталога Not found Не найдено %1 MB %1 МБ Game Information Download Загрузка информации об игре No results found. Результатов нет. No more results found. Больше результатов нет. Download Complete! Загрузка завершена! RomCollection Warning Внимание No ROMs found in Файлы ROM не найдены в No ROMs found. Файлы ROM не найдены. Database Not Loaded База данных не загружена Could not connect to Sqlite database. Application may misbehave. Не удалось подключиться к базе данных SQLite. Некоторые функции программы могут не работать. Loading ROMs... Загрузка файлов ROM... Cancel Отмена SettingsDialog Settings Настройки Paths Пути Browse... Обзор... ROM Directories Каталоги файлов ROM Add... Добавить... Remove Удалить Emulation Эмуляция Table Таблица Image: Изображение: ... ... Current Columns: Текущие столбцы: Stretch First Column Растягивать первый столбец Available Columns: Доступные столбцы: Save Files Сохранить файлы 16 kbit EEPROM file: Файл EEPROM 16 кбит: FlashRAM file: Файл FlashRAM: Save directory: Каталог сохранений: 4 kbit EEPROM file: Файл EEPROM 4 кбит SRAM file: Файл SRAM: Specify individual files for saves Указать индивидуальные файлы для сохранений: CEN64 Files Файлы CEN64 PIF IPL file: Файл PIF IPL: ROM catalog: Каталог файлов ROM: CEN64 executable: Исполняемый файл CEN64: 64DD IPL file: Файл 64DD IPL: CEN64-Qt will search for all .z64, .n64, .ndd, and .zip files in these directories CEN64-Qt будет искать все файлы Z64, V64, N64, NDD и ZIP в этих каталогах Run multithreaded (quasi-accurate): Многопоточный запуск (сниженная точность): No audio: Отключить звук: No video: Отключить видео: Controllers Контроллеры Controller #1 Контроллер #1 Transfer Pak ROM: Файл ROM Transfer Pak: Transfer Pak Save: Файл сохранения Transfer Pak: Controller Pak File: Файл Controller Pak: Accessory: Аксессуар: Enabled: Включено: Controller #2 Контроллер #2 Controller #3 Контроллер #3 Controller #4 Контроллер #4 Grid Сетка ROM Label Метка ROM Show Label: Показать метку: Label Text: Текст метки: Label Color: Цвет метки: Other Другое Custom Background: Пользовательский фон: Sorting: Сортировка: Descending По убыванию Background: Фон: ROM Image Изображение ROM Shadow Inactive Color: Цвет неактивной тени: Shadow Active Color: Цвет активной тени: Columns: Столбцы: Image Size: Размер изображения: Adjust Automatically Выравнивать автоматически List Список First Item as Header Первый элемент в качестве заголовка Requires catalog file Требуется файл каталога Current Items: Текущие элементы: Available Items: Доступные элементы: Display Cover Image Отображать изображение обложки Text Size: Размер текста: Theme: Тема: Additional Parameters: Дополнительные настройки: Download Game Information (thegamesdb.net): Загрузить информацию об игре (thegamesdb.net): Output to Console: Вывод в консоль: Use to pass additional parameters to CEN64 Используйте для передачи дополнительных параметров в CEN64 Language: Язык: <b>Note:</b> Language changes will not take place until application restart. <b>Примечание:</b> Язык изменится только после перезапуска программы. None Нет Rumble Pak Rumble Pak Controller Pak Controller Pak Transfer Pak Transfer Pak Extra Small Очень маленький Small Маленький Medium Средний Large Большой Extra Large Очень большой Super Супер Black Черный White Белый Light Gray Светло-серый Dark Gray Темно-серый Green Зеленый Cyan Голубой Blue Синий Purple Пурпурный Red Красный Pink Розовый Orange Оранжевый Yellow Желтый Brown Коричневый Light Светлый Normal Обычный Dark Темный OK OK Cancel Отмена ROM Directory Каталог файлов ROM 64DD IPL ROM File Файл 64DD IPL ROM Catalog File Файл каталога <ParentName> Executable Исполняемый файл <ParentName> 4kbit EEPROM File Файл 4kbit EEPROM 16kbit EEPROM File Файл 16kbit EEPROM FlashRAM File Файл FlashRAM Controller Pak File Файл Controller Pak PIF IPL ROM File Файл PIF IPL ROM Save Directory Каталог сохранений SRAM File Файл SRAM Transfer Pak ROM File Файл ROM Transfer Pak Transfer Pak Save File Файл сохранения Transfer Pak Background Image Фоновое изображение TableView No Cart Нет картриджа TheGamesDBScraper <b>NOTE:</b> If you are deleting this game's information because the game doesn't <b>Примечание:</b> Если вы удаляете информацию об этой игре, поскольку ее better to create an account on лучше создать аккаунт на and add the game so other users can benefit as well. и добавить игру. Это поможет другим пользователям. exist on TheGamesDB and <AppName> pulled the information for different game, it's нет на TheGamesDB и <AppName> выдает информацию о другой игре, This will cause <AppName> to not update the information for this game until you <AppName> не будет обновлять информацию для этой игры, пока вы force it with "Download/Update Info..." не выполните "Загрузка/Обновить информацию..." Delete the current information for Удалить текущую информацию для Delete Game Information Удалить информацию об игре The following error from TheGamesDB occured while downloading: Во время загрузки с TheGamesDB произошла ошибка: Released on: Выпущено на: Does this look correct? Все верно? Request timed out. Check your network settings. Время запроса истекло. Проверьте настройки сети. Continue scraping information? Продолжить получение информации? Network Error Ошибка сети V64Converter Open v64/n64 File Открыть файл v64/n64 V64/n64 ROMs Файлы ROM V64/n64 All Files Все файлы Save z64 File Сохранить файл Z64 Z64 ROMs Файлы ROM Z64 already in z64 format! уже в формате Z64! <AppName> Converter Конвертер <AppName> Conversion complete! Конвертирование завершено! is not a valid .v64 or .n64 file! не является корректным файлом V64 или N64! ./resources/locale/cen64-qt_fr.qm0000664000175000017500000005415613623376647015177 0ustar dandan$ ?mۄMJ(fZ#tU! GZ35h7&Ѫ&n()5"Iz=NP7;_n {5'0%/:6ڶ `=m$A 4d>iPu_;ypD͋`(Ԣ /S2'4R KV   !& "K;O )I#-U 0r IA+/ IB+s IC+ ID+ QZ ewO w |A H' - /H Fj, I e ̚7  (Ѯm ) QW d μ μ: ^@ n (0 * u # Z% s s= , ɠ ʧs 1 ޴d %C E &SO 0% XV> [I E" P D P 29 ::# 0 2w4B:BxLLN+/oNw4e,5NuʎBsB1j' z- P`9R/9aY5eG#i website AboutDialogUne interface graphique simple et lmentaire pour <ParentName>.2A customizable launcher for using Qt. AboutDialog* propos de <AppName>About  AboutDialogDpt GitHubGitHub repository AboutDialogVersionVersion AboutDialogPas de disqueNo DiskDDViewAnnulerCancelDownloadDialogFichierFileDownloadDialog:URL du jeu sur thegamesdb.netFrom thegamesdb.net URL of gameDownloadDialogNom du jeu : Name of Game:DownloadDialogChercherSearchDownloadDialogHChercher des informations sur le jeuSearch Game InformationDownloadDialog.ou identifiant du jeu : or Game ID:DownloadDialog4Fichier 64DD IPL manquant.64DD IPL file not found.EmulatorHandler*ROM 64DD introuvable.64DD ROM file not found.EmulatorHandler 64DD non activ.64DD not enabled.EmulatorHandlerFExecutable <ParentName> non trouv." executable not found.EmulatorHandler<ParentName> a quitt de manire inattendue. Consultez les journaux pour plus d'informations.C quit unexpectedly. Check the log for more information.EmulatorHandler$mulation dmarreEmulation startedEmulatorHandler"mulation arrteEmulation stoppedEmulatorHandlerXAucune ROM slectionne ou 64 DD non activ.$No ROM selected or 64DD not enabled.EmulatorHandler0Aucune ROM slectionne.No ROM selected.EmulatorHandlerBN'est pas un fichier 64DD valide.Not a valid 64DD File.EmulatorHandler@N'est pas un fichier Z64 valide.Not a valid Z64 File.EmulatorHandler6Fichier PIF IPL non trouv.PIF IPL file not found.EmulatorHandler*Fichier ROM manquant.ROM file not found.EmulatorHandler2Consulter les journaux... View Log...EmulatorHandlerAttentionWarningEmulatorHandler Pas de cartoucheNo CartGridView Pas de cartoucheNo CartListView*Journaux <ParentName> Log LogDialog FermerClose LogDialog &propos&About MainWindow&Configurer... &Configure... MainWindow>&Convertir au format V64/n64...&Convert V64/n64... MainWindowN&Tlcharger/Mettre jour les infos...&Download/Update Info... MainWindow&mulation &Emulation MainWindow&Fichier&File MainWindowPl&ein-cran &Full-screen MainWindow &Aide&Help MainWindow&Disposition&Layout MainWindow$&Ouvrir une ROM... &Open ROM... MainWindow&Quitter&Quit MainWindow&&Recharger la liste &Refresh List MainWindow&Paramtres &Settings MainWindow&Dmarrer&Start MainWindow &Barre de statut &Status Bar MainWindow&Affichage&View MainWindow"Tous les fichiers All Files MainWindowAnnulerCancel MainWindow>&Effacer les infos actuelles...D&elete Current Info... MainWindowActiver 64DD Enable 64DD MainWindow Vue en vignettes Grid View MainWindow LancerLaunch MainWindowVue en liste List View MainWindowPas de sortie No Output MainWindowAucune ROMNo ROMs MainWindowNAucune ROM trouve dans le fichier ZIP.No ROMs found in ZIP file. MainWindow AucuneNone MainWindow*Ouvrir un fichier ROM Open ROM File MainWindowChoisir une ROM Select ROM MainWindow&ArrterSt&op MainWindowVue en tableau Table View MainWindowIl n'y a pas de journaux. Soit <ParentName> n a pas encore t lanc, soit il n y a pas de donnes concernant le dernier dmarrage. ^There is no log. Either has not yet run or there was no output from the last run. MainWindow2Consulter les journaux... View Log... MainWindow%1 - MB%1 MBQObjectCRC1CRC1QObjectCRC2CRC2QObjectDveloppeur DeveloperQObject0Tlchargement termin !Download Complete!QObjectESRBESRBQObjectNom du fichierFilenameQObject4Nom du fichier (extension)Filename (extension)QObject Game CoverQObjectNTlcharger des informations sur le jeuGame Information DownloadQObjectTitre du jeu Game TitleQObject GenreGenreQObjectNom GoodGoodNameQObjectNom interne Internal NameQObjectMD5MD5QObject*Pas d'autre rsultat.No more results found.QObjectAucun rsultat.No results found.QObjectNon trouv Not foundQObject AperuOverviewQObjectJoueursPlayersQObjectditeur PublisherQObjectCertificationRatingQObjectDate de sortie Release DateQObject2Fichier catalogue demandRequires catalog fileQObject RumbleRumbleQObject$Type de sauvegarde Save TypeQObject TailleSizeQObjectROM inconnue Unknown ROMQObjectFichier ZipZip FileQObjectAnnulerCancel RomCollectionImpossible de se connecter la base de donnes Sqlite. L'application peut dysfonctionner.@Could not connect to Sqlite database. Application may misbehave. RomCollection6Base de donnes non chargeDatabase Not Loaded RomCollection,Chargement des ROMS...Loading ROMs... RomCollection.Aucune ROM trouve dansNo ROMs found in  RomCollection&Aucune ROM trouve.No ROMs found. RomCollectionAttentionWarning RomCollection......SettingsDialog0Fichier EEPROM 16 kbit :16 kbit EEPROM file:SettingsDialog,Fichier EEPROM 16 kbit16kbit EEPROM FileSettingsDialog.Fichier EEPROM 4 kbit :4 kbit EEPROM file:SettingsDialog*Fichier EEPROM 4 kbit4kbit EEPROM FileSettingsDialog(Fichier ROM 64DD IPL64DD IPL ROM FileSettingsDialog$Fichier IPL 64DD :64DD IPL file:SettingsDialog>Fichier excutable <ParentName> ExecutableSettingsDialog<b>Remarque : </b>Le changement de langue ne prendra effet qu'aprs le redmarrage du programme.LNote: Language changes will not take place until application restart.SettingsDialogAccessoire : Accessory:SettingsDialogAjouter...Add...SettingsDialog:Paramtres supplmentaires : Additional Parameters:SettingsDialog(Ajustage automatiqueAdjust AutomaticallySettingsDialog,Colonnes disponibles :Available Columns:SettingsDialog.Intituls disponibles :Available Items:SettingsDialog(Image d'arrire-planBackground ImageSettingsDialogArrire-plan: Background:SettingsDialogNoirBlackSettingsDialogBleuBlueSettingsDialog MarronBrownSettingsDialogNaviguer... Browse...SettingsDialogFichier CEN64 CEN64 FilesSettingsDialog$Excutable CEN64 :CEN64 executable: SettingsDialogCEN64-Qt cherchera tous les fichiers z64, n64, ndd et zip dans ces dossiersRCEN64-Qt will search for all .z64, .n64, .ndd, and .zip files in these directoriesSettingsDialogAnnulerCancelSettingsDialog"Fichier catalogue Catalog FileSettingsDialog(Nombre de colonnes :Columns:SettingsDialogManette #1 Controller #1SettingsDialogManette #2 Controller #2SettingsDialogManette #2 Controller #3SettingsDialogManette #4 Controller #4SettingsDialogController pakController PakSettingsDialog,Fichier Controller pakController Pak FileSettingsDialog0Fichier Controller pak :Controller Pak File:SettingsDialogManette ControllersSettingsDialog(Colonnes utilises :Current Columns:SettingsDialog"Intitul actuel :Current Items:SettingsDialog2Choisir un arrire-plan :Custom Background:SettingsDialogCyanCyanSettingsDialog SombreDarkSettingsDialogGris fonc Dark GraySettingsDialogDescendant DescendingSettingsDialog,Afficher les vignettesDisplay Cover ImageSettingsDialogxTlcharger les informations sur les jeux (thegamesdb.net) :+Download Game Information (thegamesdb.net):SettingsDialogmulation EmulationSettingsDialogActive :Enabled:SettingsDialogTrs grande Extra LargeSettingsDialogTrs petite Extra SmallSettingsDialog<Premier intitul comme en-tteFirst Item as HeaderSettingsDialog Fichier FlashRAM FlashRAM FileSettingsDialog$Fichier FlashRAM :FlashRAM file:SettingsDialogVertGreenSettingsDialogVignettesGridSettingsDialog&Taille des images : Image Size:SettingsDialogImage :Image:SettingsDialog.Couleur de l'intitul : Label Color:SettingsDialog*Texte de l'intitul : Label Text:SettingsDialogLangue : Language:SettingsDialog GrandeLargeSettingsDialogLumireLightSettingsDialogGris clair Light GraySettingsDialog ListeListSettingsDialogMoyenneMediumSettingsDialog&Dsactiver le son : No audio:SettingsDialog*Dsactiver la vido : No video:SettingsDialog AucunNoneSettingsDialogNormaleNormalSettingsDialog OrangeOrangeSettingsDialog AutreOtherSettingsDialog0Sortie vers la console :Output to Console:SettingsDialog&Fichier ROM PIF IPLPIF IPL ROM FileSettingsDialog"Fichier PIF IPL : PIF IPL file:SettingsDialogCheminsPathsSettingsDialogRosePinkSettingsDialog VioletPurpleSettingsDialog Dossier des ROMSROM DirectoriesSettingsDialog"Dossiers des ROMS ROM DirectorySettingsDialogImages des ROMS ROM ImageSettingsDialog$Intitul de la ROM ROM LabelSettingsDialog(Catalogue des ROMS : ROM catalog:SettingsDialog RougeRedSettingsDialogSupprimerRemoveSettingsDialog2Fichier catalogue demandRequires catalog fileSettingsDialogKit vibration Rumble PakSettingsDialogPMultithread (prcision quasi-parfaite) :#Run multithreaded (quasi-accurate):SettingsDialogFichier SRAM SRAM FileSettingsDialogFichier SRAM : SRAM file:SettingsDialog*Dossier de sauvegardeSave DirectorySettingsDialog,Fichiers de sauvegarde Save FilesSettingsDialog.Dossier de sauvegarde :Save directory:SettingsDialogParamtresSettingsSettingsDialogVCouleur de l ombre de la vignette active : Shadow Active Color:SettingsDialogXCouleur de l ombre des vignettes inactives :Shadow Inactive Color:SettingsDialog(Montrer l'intitul : Show Label:SettingsDialog PetiteSmallSettingsDialogClassement :Sorting:SettingsDialoghAttribuer un fichier spcifique pour les sauvegardes"Specify individual files for savesSettingsDialog6tendre la premire colonneStretch First ColumnSettingsDialogTableauTableSettingsDialog Taille du texte: Text Size:SettingsDialog Thme:Theme:SettingsDialogTransfer pak Transfer PakSettingsDialog0Fichier ROM Transfer pakTransfer Pak ROM FileSettingsDialog$Rom Transfer pak :Transfer Pak ROM:SettingsDialogDFichier de sauvegarde Transfer pakTransfer Pak Save FileSettingsDialogTransfer Pak Save:SettingsDialog\Ajouter des paramtres supplmentaires CEN64*Use to pass additional parameters to CEN64SettingsDialog BlancWhiteSettingsDialog JauneYellowSettingsDialog Pas de cartoucheNo Cart TableView<b>Remarque :</b> Si vous effacez les informations de ce jeu parce que le jeuRNOTE: If you are deleting this game's information because the game doesn't TheGamesDBScraperNContinuer rcolter des informations ?Continue scraping information?TheGamesDBScraper>Effacer les informations du jeuDelete Game InformationTheGamesDBScraperNEffacer les informations actuelles pour"Delete the current information forTheGamesDBScraper`Ces informations vous semblent-elles correctes ?Does this look correct?TheGamesDBScraper Network ErrorTheGamesDBScraperPubli le : Released on: TheGamesDBScraperhTemps dpass. Vrifier vos paramtres de connexion./Request timed out. Check your network settings.TheGamesDBScraper<AppName> ne mettra pas jour les informations jusqu' ce que vous PThis will cause to not update the information for this game until you TheGamesDBScraperet d ajouter le jeu afin d en faire profiter galement les autres utilisateurs.4and add the game so other users can benefit as well.TheGamesDBScraperHil est prfrable de crer un comptebetter to create an account onTheGamesDBScrapern est pas dans TheGamesDB et que <AppName> vous propose des informations pour un autre jeu, Rexist on TheGamesDB and pulled the information for different game, it's TheGamesDBScraperjrelanciez le "Tlcharger/Mettre jour les infos..."'force it with "Download/Update Info..."TheGamesDBScraper.Convertisseur <AppName> Converter V64Converter"Tous les fichiers All Files V64Converter*Conversion termine !Conversion complete! V64Converter2Ouvrir un fichier v64/n64Open v64/n64 File V64Converter4Sauvegarder le fichier z64 Save z64 File V64ConverterROMS V64/n64 V64/n64 ROMs V64ConverterROMS Z64Z64 ROMs V64Converter(Dj au format z64 !already in z64 format! V64ConverterJn est pas un fichier v64/n64 valide !!is not a valid .v64 or .n64 file! V64Converter./resources/locale/cen64-qt_fr.ts0000664000175000017500000015323113623376647015202 0ustar dandan AboutDialog About <AppName> À propos de <AppName> Version Version A customizable launcher for <ParentName> using Qt. Une interface graphique simple et élémentaire pour <ParentName>. <ParentName> website Site web de <ParentName> GitHub repository Dépôt GitHub DDView No Disk Pas de disque DownloadDialog Search Game Information Chercher des informations sur le jeu File Fichier Name of Game: Nom du jeu : or Game ID: ou identifiant du jeu : From thegamesdb.net URL of game URL du jeu sur thegamesdb.net Search Chercher Cancel Annuler EmulatorHandler Warning Attention View Log... Consulter les journaux... Emulation stopped Émulation arrêtée <ParentName> quit unexpectedly. Check the log for more information. <ParentName> a quitté de manière inattendue. Consultez les journaux pour plus d'informations. <ParentName> executable not found. Executable <ParentName> non trouvé. PIF IPL file not found. Fichier PIF IPL non trouvé. 64DD IPL file not found. Fichier 64DD IPL manquant. ROM file not found. Fichier ROM manquant. 64DD ROM file not found. ROM 64DD introuvable. No ROM selected. Aucune ROM sélectionnée. 64DD not enabled. 64DD non activé. Not a valid Z64 File. N'est pas un fichier Z64 valide. Not a valid 64DD File. N'est pas un fichier 64DD valide. No ROM selected or 64DD not enabled. Aucune ROM sélectionnée ou 64 DD non activé. Emulation started Émulation démarrée GridView No Cart Pas de cartouche ListView No Cart Pas de cartouche LogDialog <ParentName> Log Journaux <ParentName> Close Fermer MainWindow &File &Fichier &Open ROM... &Ouvrir une ROM... &Refresh List &Recharger la liste &Download/Update Info... &Télécharger/Mettre à jour les infos... D&elete Current Info... &Effacer les infos actuelles... &Quit &Quitter &Emulation &Émulation &Start &Démarrer St&op &Arrêter Enable 64DD Activer 64DD View Log... Consulter les journaux... &Settings &Paramètres &Configure... &Configurer... &View &Affichage &Layout &Disposition None Aucune Table View Vue en tableau Grid View Vue en vignettes List View Vue en liste &Full-screen Pl&ein-Écran &Status Bar &Barre de statut &Help &Aide &About À &propos Add a directory containing ROMs under Settings->Configure->Paths to use this view. There is no log. Either <ParentName> has not yet run or there was no output from the last run. Il n'y a pas de journaux. Soit <ParentName> n’a pas encore été lancé, soit il n’y a pas de données concernant le dernier démarrage. Cancel Annuler No Output Pas de sortie &Convert V64/n64... &Convertir au format V64/n64... All Files Tous les fichiers Open ROM File Ouvrir un fichier ROM No ROMs Aucune ROM No ROMs found in ZIP file. Aucune ROM trouvée dans le fichier ZIP. Select ROM Choisir une ROM Launch Lancer QObject GoodName Nom Good Filename Nom du fichier Filename (extension) Nom du fichier (extension) Zip File Fichier Zip Internal Name Nom interne Size Taille MD5 MD5 CRC1 CRC1 CRC2 CRC2 Players Joueurs Rumble Rumble Save Type Type de sauvegarde Game Title Titre du jeu Release Date Date de sortie Overview Aperçu ESRB ESRB Genre Genre Publisher Éditeur Developer Développeur Rating Certification Game Cover Unknown ROM ROM inconnue Requires catalog file Fichier catalogue demandé Not found Non trouvé %1 MB %1 - MB Game Information Download Télécharger des informations sur le jeu No results found. Aucun résultat. No more results found. Pas d'autre résultat. Download Complete! Téléchargement terminé ! RomCollection Warning Attention No ROMs found in Aucune ROM trouvée dans No ROMs found. Aucune ROM trouvée. Database Not Loaded Base de données non chargée Could not connect to Sqlite database. Application may misbehave. Impossible de se connecter à la base de données Sqlite. L'application peut dysfonctionner. Loading ROMs... Chargement des ROMS... Cancel Annuler SettingsDialog Settings Paramètres Paths Chemins Save Files Fichiers de sauvegarde Browse... Naviguer... 16 kbit EEPROM file: Fichier EEPROM 16 kbit : FlashRAM file: Fichier FlashRAM : Save directory: Dossier de sauvegarde : Specify individual files for saves Attribuer un fichier spécifique pour les sauvegardes 4 kbit EEPROM file: Fichier EEPROM 4 kbit : SRAM file: Fichier SRAM : CEN64 Files Fichier CEN64 PIF IPL file: Fichier PIF IPL : ROM catalog: Catalogue des ROMS : CEN64 executable: Exécutable CEN64 : 64DD IPL file: Fichier IPL 64DD : ROM Directories Dossier des ROMS CEN64-Qt will search for all .z64, .n64, .ndd, and .zip files in these directories CEN64-Qt cherchera tous les fichiers z64, n64, ndd et zip dans ces dossiers Add... Ajouter... Remove Supprimer Emulation Émulation Run multithreaded (quasi-accurate): Multithread (précision quasi-parfaite) : No audio: Désactiver le son : No video: Désactiver la vidéo : Controllers Manette Controller #1 Manette #1 Transfer Pak ROM: Rom Transfer pak : Transfer Pak Save: Controller Pak File: Fichier Controller pak : Accessory: Accessoire : Enabled: Activée : Controller #2 Manette #2 Controller #3 Manette #2 Controller #4 Manette #4 Table Tableau Image: Image : ... ... Current Columns: Colonnes utilisées : Stretch First Column Étendre la première colonne Available Columns: Colonnes disponibles : Grid Vignettes ROM Label Intitulé de la ROM Show Label: Montrer l'intitulé : Label Text: Texte de l'intitulé : Label Color: Couleur de l'intitulé : Other Autre Custom Background: Choisir un arrière-plan : Sorting: Classement : Descending Descendant Background: Arrière-plan: ROM Image Images des ROMS Shadow Inactive Color: Couleur de l’ombre des vignettes inactives : Shadow Active Color: Couleur de l’ombre de la vignette active : Columns: Nombre de colonnes : Image Size: Taille des images : Adjust Automatically Ajustage automatique List Liste First Item as Header Premier intitulé comme en-tête Current Items: Intitulé actuel : Available Items: Intitulés disponibles : Display Cover Image Afficher les vignettes Text Size: Taille du texte: Theme: Thème: Additional Parameters: Paramètres supplémentaires : Requires catalog file Fichier catalogue demandé Download Game Information (thegamesdb.net): Télécharger les informations sur les jeux (thegamesdb.net) : Output to Console: Sortie vers la console : Use to pass additional parameters to CEN64 Ajouter des paramètres supplémentaires à CEN64 Language: Langue : <b>Note:</b> Language changes will not take place until application restart. <b>Remarque : </b>Le changement de langue ne prendra effet qu'après le redémarrage du programme. None Aucun Rumble Pak Kit vibration Controller Pak Controller pak Transfer Pak Transfer pak Extra Small Très petite Small Petite Medium Moyenne Large Grande Extra Large Très grande Super Black Noir White Blanc Light Gray Gris clair Dark Gray Gris foncé Green Vert Cyan Cyan Blue Bleu Purple Violet Red Rouge Pink Rose Orange Orange Yellow Jaune Brown Marron Light Lumière Normal Normale Dark Sombre OK Cancel Annuler ROM Directory Dossiers des ROMS 64DD IPL ROM File Fichier ROM 64DD IPL Background Image Image d'arrière-plan Catalog File Fichier catalogue <ParentName> Executable Fichier exécutable <ParentName> 4kbit EEPROM File Fichier EEPROM 4 kbit 16kbit EEPROM File Fichier EEPROM 16 kbit FlashRAM File Fichier FlashRAM Controller Pak File Fichier Controller pak PIF IPL ROM File Fichier ROM PIF IPL Save Directory Dossier de sauvegarde SRAM File Fichier SRAM Transfer Pak ROM File Fichier ROM Transfer pak Transfer Pak Save File Fichier de sauvegarde Transfer pak TableView No Cart Pas de cartouche TheGamesDBScraper <b>NOTE:</b> If you are deleting this game's information because the game doesn't <b>Remarque :</b> Si vous effacez les informations de ce jeu parce que le jeu better to create an account on il est préférable de créer un compte and add the game so other users can benefit as well. et d’ajouter le jeu afin d’en faire profiter également les autres utilisateurs. exist on TheGamesDB and <AppName> pulled the information for different game, it's n’est pas dans TheGamesDB et que <AppName> vous propose des informations pour un autre jeu, This will cause <AppName> to not update the information for this game until you <AppName> ne mettra pas à jour les informations jusqu'à ce que vous force it with "Download/Update Info..." relanciez le "Télécharger/Mettre à jour les infos..." Delete the current information for Effacer les informations actuelles pour Delete Game Information Effacer les informations du jeu The following error from TheGamesDB occured while downloading: Released on: Publié le : Does this look correct? Ces informations vous semblent-elles correctes ? Request timed out. Check your network settings. Temps dépassé. Vérifier vos paramètres de connexion. Continue scraping information? Continuer à récolter des informations ? Network Error Erreur réseau V64Converter Open v64/n64 File Ouvrir un fichier v64/n64 V64/n64 ROMs ROMS V64/n64 All Files Tous les fichiers Save z64 File Sauvegarder le fichier z64 Z64 ROMs ROMS Z64 already in z64 format! Déjà au format z64 ! is not a valid .v64 or .n64 file! n’est pas un fichier v64/n64 valide ! <AppName> Converter Convertisseur <AppName> Conversion complete! Conversion terminée ! ./README.md0000664000175000017500000000452013623376647010604 0ustar dandan# CEN64-Qt [![Build Status](https://travis-ci.org/dh4/cen64-qt.svg?branch=master)](https://travis-ci.org/dh4/cen64-qt) A customizable cross-platform frontend for [CEN64](https://github.com/n64dev/cen64). ![CEN64-Qt Grid View](https://raw.githubusercontent.com/dh4/cen64-qt/master/resources/demos/main.jpg) ## Getting CEN64-Qt ### Release Builds Release builds can be found on the [releases](https://github.com/dh4/cen64-qt/releases) page. Note these are currently only alpha releases versioned with the date. I don't plan to adopt a version scheme until CEN64 is more stable. ### Development Builds Automatic builds of the latest git commit can be downloaded here: Linux: [cen64-qt_linux_git-latest.tar.gz](https://s3.amazonaws.com/dh4/cen64-qt/master/cen64-qt_linux_git-latest.tar.gz) Windows: [cen64-qt_win_git-latest.zip](https://s3.amazonaws.com/dh4/cen64-qt/master/cen64-qt_win_git-latest.zip) OSX: [cen64-qt_osx_git-latest.dmg](https://s3.amazonaws.com/dh4/cen64-qt/master/cen64-qt_osx_git-latest.dmg) Be aware that these may contain bugs not present in the release builds. ### Building (Linux) First, obtain the source code for CEN64-Qt. You can either clone the repository with git or download an archive of the source code for a specific release from the [releases](https://github.com/dh4/cen64-qt/releases) page. You'll need to make sure you have qmake, g++, the Qt development libraries and the QuaZIP development files installed. On Debian/Ubuntu, this can be accomplished by: ``` # apt-get install qt5-qmake g++ qtbase5-dev libquazip-qt5-dev libqt5sql5-sqlite ``` Once the needed packages are installed, create the Makefile with qmake and then build with make. Run the following commands from the directory that contains cen64-qt.pro: ``` $ qmake $ make ``` If qmake returns "qmake: could not find a Qt installation of ''", you can try running `QT_SELECT=qt5 qmake` or `/usr/lib/x86_64-linux-gnu/qt5/bin/qmake`. Some distributions also contain a `qmake-qt5` symlink. #### Compiling QuaZIP statically You also have the option to compile QuaZIP statically. Download the QuaZIP sources from Sourceforge. Place the contents of `quazip-/quazip/` in `quazip5/` within the project directory. Then run: ``` $ qmake CONFIG+=linux_quazip_static $ make ``` You will see warnings after the qmake step if the QuaZIP sources are in the wrong place. ./VERSION0000664000175000017500000000000613623376647010370 0ustar dandan(none)./.travis.yml0000664000175000017500000000533713623376647011445 0ustar dandanlanguage: cpp sudo: required dist: bionic env: matrix: - BUILD=windows - BUILD=native os: - linux - osx matrix: exclude: - os: osx env: BUILD=windows install: - "./build-scripts/travis-ci.sh setup_qt" - "./build-scripts/travis-ci.sh get_quazip" script: - "./build-scripts/travis-ci.sh build" before_deploy: - "./build-scripts/travis-ci.sh package" deploy: - provider: s3 access_key_id: secure: GsN4LS6DXbwkMlUchj6RX/E/id4g+PTjFYzwATjVuk+Yilipdn/vpGWGLthJpNN9ontgXaTI9+R8vBgAxR6IRmmMHGafYQVBnH0n/RwA9qG4UTV5DEO/Vje2fXUAIxsinHG7jqJewIOAODvfkj4uG+DgUUv9V3BgWUl0whPyoAlkoXlZJ6iCWrCdgQ6RSXKqIJbaIjd1m5AvATXVTkpW2V6Fqk0/uTZDAM7pGVpY3Ddkpo1Xt6djRyz1W9Y9GyxxcUh9ecyeK2TdUoy8bvm43EnCNtLbOqSdkF1zWWU7uguDfYXw5tHtxf5BWbP2H1addGdExLHsFvG88zW0YhxzLWEhKaPQOvTstMZ1QUvP2b5pUVE/VyRF7he2xoG3X8UOHk0KKgO+8BxQo5sydt/91IuyJ9DVZxmHHWvIrOm5PJxF82RfmVxw8lTR5mb8Y9A0cN+KBa7QaLwPo+vLOqREIQh2Vk12aP/9GXwZF0NLv2vusvoosOJ9NsTR6gtMKY8nkbBLTLnxWNm94OEm2dLAL9qc4Gl5InitVEoJA51eK+EVpvdVWnqaC+TSL8482Xly+hYIyBOkBgywkP2FEszonleK/BMu7J+fEmoJz2FtnTumGOszm8hioy/p2eGv8ilgpeIP9yq4CPVBdoucVI4sNeq53VF6jyexhfF/zkkBPOk= secret_access_key: secure: tUdQW5zil2MP3vYQFYEF7Y6bUeADbvr36S8EJdNdAsxu+1dRSdLsUg04qzD8bNb7GN1Ez+b3XiEc5TZx4zRZ44O8I/Sdggcpb5JqUhOJjwQIq3VFBsJll4OIJkt536sSh2S/YpYkIU45npatcHZVrxxqy/Gs4R89tyltwzjy6bMgQjOF8bRaXZnu4Yz8pn7GyVCHAXJ4Y+09su7qZnTjixe9097SgJMAzSLh8aqBIqkT0ULuU8ePaM1xOIwMuq7PjYi/QtDN19yOyGIHt8ceQ1VA16uio1UdOys4PHKY+2a9WMcT344OGLN5pQ5qYLuJYnFrjlnW2fBWfM9CG1Tua+jjiegyLMl7FPxja1V5ML2q/EzZemQkpoy9AiJQUO+8AMACK7osSFUIfx2f+Ppj2sjCXgb0FJtey0kSiATLsHBFx5lAlkGuv2vTM090bOQ6zWoymmIu5O9rnox2fh5PS5XfJ0yB9BVWHBC9p1VYRq17/keOPTDcsdXVd6gkNzdDlDgKEqrwcEltYynWEL/qQqVOFYj5thxpaxG79z5nKnGew87gigr5iHp8/B4OqzzGwMovSYYLmS3ihAycuwzHdUeXoZ+r+PyYjqGvMZURVemqXsz/mXkbH5NdgXOd7Q7ZStvs3X9uzqq7LaCrKiYaGLeVWEY/YB8cvGLWW4uS8gw= bucket: dh4 skip_cleanup: true local_dir: build upload-dir: cen64-qt on: all_branches: true condition: $TRAVIS_PULL_REQUEST = false - provider: releases api_key: secure: dqCRa1PQCU/QyDNMdt1Ejz8FMDuEipNTxM30vwS8Hd6gj7w/Q9QWBnpCSevOi0b+Nx76hS2FEKe/u7vbqlE1Pvd7bSPOQ0wi7SWcldwux2QGRFuoWX7ByMKKB/rR8RaKkx0W1yhk9ipkiIOZkvF1gTWGAkLLim8wm3ax6EKNB7tKqb9Sqc6ZYwOb9u9tO8r3ZufRKcBU3Vy1BEkP0jZFKYcGbuk7u/Ra9E3/VwxLd5qh8z7CfGNp7Rw4+CnhHTbhlWJicZwaM0IauL9jQw7nqKKjfCsdUZUB9lF7gLCs6/Sdca9rH2ErZiKVeMIe317K/xcKUPTBt3AbFrPdDKNq54Ty+gGfSeJbaxos5Wn7h5s+hKMDTIUpPv9suO5M8spDT2P+yuqKH4tdDQa51AsxudRNpnYCCfBlUg4Z1Ew2sTst24eNz+7owXBX/eCoHxklkmHTU/pE1VoRY2FleJg9mTNy6NjFb5oENjbCfKeaP4bnyg2tVJvUr2RoS5hBVOBtBA/NbdI7SC3yStrAlyrtqBS0wZcoUiXniLYS28YiZH1d8rZYH9YumIzq7Ue7T1SEjS7WJZM2Ng6STXkP4j1zrcUfnk22LeaeB6ULTH1lmdfRxmFECKDSEyvwj60Q6GDxLGUtqlBoUoQjSXwO4E7x0jN/oNsPHjkElJaOuqzZ/xc= skip_cleanup: true file_glob: true file: build/*/cen64-qt_* on: tags: true ./cen64-qt.pro0000664000175000017500000000427413623376647011416 0ustar dandanQT += core network xml sql lessThan(QT_MAJOR_VERSION, 5) { QT += gui } else { QT += widgets } macx { TARGET = CEN64-Qt } else { TARGET = cen64-qt } TEMPLATE = app macx:ICON = dist/macosx/cen64.icns win32:RC_FILE = dist/windows/icon.rc SOURCES += src/main.cpp \ src/common.cpp \ src/mainwindow.cpp \ src/dialogs/aboutdialog.cpp \ src/dialogs/downloaddialog.cpp \ src/dialogs/logdialog.cpp \ src/dialogs/settingsdialog.cpp \ src/dialogs/v64converter.cpp \ src/emulation/emulatorhandler.cpp \ src/roms/romcollection.cpp \ src/roms/thegamesdbscraper.cpp \ src/views/gridview.cpp \ src/views/listview.cpp \ src/views/tableview.cpp \ src/views/ddview.cpp \ src/views/widgets/clickablewidget.cpp \ src/views/widgets/treewidgetitem.cpp HEADERS += src/global.h \ src/common.h \ src/mainwindow.h \ src/dialogs/aboutdialog.h \ src/dialogs/downloaddialog.h \ src/dialogs/logdialog.h \ src/dialogs/settingsdialog.h \ src/dialogs/v64converter.h \ src/emulation/emulatorhandler.h \ src/roms/romcollection.h \ src/roms/thegamesdbscraper.h \ src/views/gridview.h \ src/views/listview.h \ src/views/tableview.h \ src/views/ddview.h \ src/views/widgets/clickablewidget.h \ src/views/widgets/treewidgetitem.h RESOURCES += resources/cen64qt.qrc FORMS += src/dialogs/settingsdialog.ui TRANSLATIONS += resources/locale/cen64-qt_fr.ts \ resources/locale/cen64-qt_ru.ts win32|macx|linux_quazip_static { CONFIG += staticlib DEFINES += QUAZIP_STATIC LIBS += -lz #Download quazip source and copy the quazip directory to project as quazip5 SOURCES += quazip5/*.cpp SOURCES += quazip5/*.c HEADERS += quazip5/*.h } else { lessThan(QT_MAJOR_VERSION, 5) { LIBS += -lquazip } else { # Debian distributions use a different library name for Qt5 quazip system("which dpkg > /dev/null 2>&1") { system("dpkg -l | grep libquazip-qt5-dev | grep ^ii > /dev/null") { LIBS += -lquazip-qt5 } else { LIBS += -lquazip5 } } else { LIBS += -lquazip5 } } } ./LICENSE0000664000175000017500000000272313623376647010335 0ustar dandanCopyright (c) 2013, Dan Hasting All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ./.gitignore0000664000175000017500000000001013623376647011303 0ustar dandan*.user* ./src/0000755000175000017500000000000013623376647010111 5ustar dandan./src/roms/0000755000175000017500000000000013623376647011071 5ustar dandan./src/roms/romcollection.cpp0000664000175000017500000004142413623376647014455 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "romcollection.h" #include "../global.h" #include "../common.h" #include "thegamesdbscraper.h" #include #include #include #include #include #include #include #include #include RomCollection::RomCollection(QStringList fileTypes, QStringList romPaths, QWidget *parent) : QObject(parent) { this->fileTypes = fileTypes; this->romPaths = romPaths; this->romPaths.removeAll(""); this->parent = parent; setupDatabase(); } Rom RomCollection::addRom(QByteArray *romData, QString fileName, QString directory, QString zipFile, QSqlQuery query, bool ddRom) { Rom currentRom; currentRom.fileName = fileName; currentRom.directory = directory; if (ddRom) currentRom.internalName = ""; else currentRom.internalName = QString(romData->mid(32, 20)).trimmed(); currentRom.romMD5 = QString(QCryptographicHash::hash(*romData, QCryptographicHash::Md5).toHex()); currentRom.zipFile = zipFile; currentRom.sortSize = romData->size(); query.bindValue(":filename", currentRom.fileName); query.bindValue(":directory", currentRom.directory); query.bindValue(":internal_name", currentRom.internalName); query.bindValue(":md5", currentRom.romMD5); query.bindValue(":zip_file", currentRom.zipFile); query.bindValue(":size", currentRom.sortSize); if (ddRom) query.bindValue(":dd_rom", 1); else query.bindValue(":dd_rom", 0); query.exec(); if (!ddRom) initializeRom(¤tRom, false); return currentRom; } int RomCollection::addRoms() { emit updateStarted(); //Count files so we know how to setup the progress dialog int totalCount = 0; foreach (QString romPath, romPaths) { QDir romDir(romPath); if (romDir.exists()) { QStringList files = scanDirectory(romDir); totalCount += files.size(); } } QList roms; QList ddRoms; database.open(); QSqlQuery query("DELETE FROM rom_collection", database); if (totalCount != 0) { int count = 0; setupProgressDialog(totalCount); query.prepare(QString("INSERT INTO rom_collection ") + "(filename, directory, internal_name, md5, zip_file, size, dd_rom) " + "VALUES (:filename, :directory, :internal_name, :md5, :zip_file, :size, :dd_rom)"); scraper = new TheGamesDBScraper(parent); foreach (QString romPath, romPaths) { QDir romDir(romPath); QStringList files = scanDirectory(romDir); int romCount = 0; foreach (QString fileName, files) { QString completeFileName = romDir.absoluteFilePath(fileName); QFile file(completeFileName); //If file is a zip file, extract info from any zipped ROMs if (QFileInfo(file).suffix().toLower() == "zip") { foreach (QString zippedFile, getZippedFiles(completeFileName)) { //check for ROM files QByteArray *romData = getZippedRom(zippedFile, completeFileName); if (fileTypes.contains("*.v64")) *romData = byteswap(*romData); if (romData->left(4).toHex() == "80371240") { //Z64 ROM roms.append(addRom(romData, zippedFile, romPath, fileName, query)); romCount++; } else if (romData->left(4).toHex() == "e848d316") { //64DD ROM ddRoms.append(addRom(romData, zippedFile, romPath, fileName, query, true)); romCount++; } delete romData; } } else { //Just a normal file file.open(QIODevice::ReadOnly); QByteArray *romData = new QByteArray(file.readAll()); file.close(); if (fileTypes.contains("*.v64")) *romData = byteswap(*romData); if (romData->left(4).toHex() == "80371240") { //Z64 ROM roms.append(addRom(romData, fileName, romPath, "", query)); romCount++; } else if (romData->left(4).toHex() == "e848d316") { //64DD ROM ddRoms.append(addRom(romData, fileName, romPath, "", query, true)); romCount++; } delete romData; } count++; progress->setValue(count); QCoreApplication::processEvents(QEventLoop::AllEvents); } if (romCount == 0) QMessageBox::warning(parent, tr("Warning"), tr("No ROMs found in ") + romPath + "."); } delete scraper; progress->close(); } else if (romPaths.size() != 0) { QMessageBox::warning(parent, tr("Warning"), tr("No ROMs found.")); } database.close(); //Emit signals for regular roms std::sort(roms.begin(), roms.end(), romSorter); for (int i = 0; i < roms.size(); i++) emit romAdded(&roms[i], i); //Emit signals for 64DD roms std::sort(ddRoms.begin(), ddRoms.end(), romSorter); for (int i = 0; i < ddRoms.size(); i++) emit ddRomAdded(&ddRoms[i]); emit updateEnded(roms.size()); return roms.size(); } int RomCollection::cachedRoms(bool imageUpdated, bool onStartup) { emit updateStarted(imageUpdated); database.open(); QSqlQuery query(QString("SELECT filename, directory, md5, internal_name, zip_file, size, dd_rom ") + "FROM rom_collection", database); query.last(); int romCount = query.at() + 1; query.seek(-1); if (romCount == -1) //Nothing cached so try adding ROMs instead return addRoms(); //Check if user has data from TheGamesDB API v1 and update them to v2 data if (onStartup) { bool onV1 = false; QDir cacheDir(getCacheLocation()); if (!cacheDir.exists() && SETTINGS.value("Other/downloadinfo", "").toString() == "true") onV1 = true; if (onV1) return addRoms(); } QList roms; QList ddRoms; int count = 0; bool showProgress = false; QTime checkPerformance; while (query.next()) { Rom currentRom; currentRom.fileName = query.value(0).toString(); currentRom.directory = query.value(1).toString(); currentRom.romMD5 = query.value(2).toString(); currentRom.internalName = query.value(3).toString(); currentRom.zipFile = query.value(4).toString(); currentRom.sortSize = query.value(5).toInt(); int ddRom = query.value(6).toInt(); //Check performance of adding first item to see if progress dialog needs to be shown if (count == 0) checkPerformance.start(); if (ddRom == 1) ddRoms.append(currentRom); else { initializeRom(¤tRom, true); roms.append(currentRom); } if (count == 0) { int runtime = checkPerformance.elapsed(); //check if operation expected to take longer than two seconds if (runtime * romCount > 2000) { setupProgressDialog(romCount); showProgress = true; } } count++; if (showProgress) { progress->setValue(count); QCoreApplication::processEvents(QEventLoop::AllEvents); } } database.close(); if (showProgress) progress->close(); //Emit signals for regular roms std::sort(roms.begin(), roms.end(), romSorter); for (int i = 0; i < roms.size(); i++) emit romAdded(&roms[i], i); //Emit signals for 64DD roms std::sort(ddRoms.begin(), ddRoms.end(), romSorter); for (int i = 0; i < ddRoms.size(); i++) emit ddRomAdded(&ddRoms[i]); emit updateEnded(roms.size(), true); return roms.size(); } QStringList RomCollection::getFileTypes(bool archives) { QStringList returnList = fileTypes; if (!archives) returnList.removeOne("*.zip"); return returnList; } void RomCollection::initializeRom(Rom *currentRom, bool cached) { QSettings *romCatalog = new QSettings(parent); QString catalogFile = SETTINGS.value("Paths/catalog", "").toString(); if (catalogFile == "") { QString dataPath = SETTINGS.value("Paths/data", "").toString(); QDir dataDir(dataPath); if (QFileInfo(dataDir.absoluteFilePath("mupen64plus.ini")).exists()) catalogFile = dataDir.absoluteFilePath("mupen64plus.ini"); } QDir romDir(currentRom->directory); //Default text for GoodName to notify user currentRom->goodName = getTranslation("Requires catalog file"); currentRom->imageExists = false; bool getGoodName = false; if (QFileInfo(catalogFile).exists()) { romCatalog = new QSettings(catalogFile, QSettings::IniFormat, parent); getGoodName = true; } QFile file(romDir.absoluteFilePath(currentRom->fileName)); currentRom->romMD5 = currentRom->romMD5.toUpper(); currentRom->baseName = QFileInfo(file).completeBaseName(); currentRom->size = QObject::tr("%1 MB").arg((currentRom->sortSize + 1023) / 1024 / 1024); if (getGoodName) { //Join GoodName on ", ", otherwise entries with a comma won't show QVariant gNameRaw = romCatalog->value(currentRom->romMD5+"/GoodName",getTranslation("Unknown ROM")); currentRom->goodName = gNameRaw.toStringList().join(", "); QStringList CRC = romCatalog->value(currentRom->romMD5+"/CRC","").toString().split(" "); if (CRC.size() == 2) { currentRom->CRC1 = CRC[0]; currentRom->CRC2 = CRC[1]; } QString newMD5 = romCatalog->value(currentRom->romMD5+"/RefMD5","").toString(); if (newMD5 == "") newMD5 = currentRom->romMD5; currentRom->players = romCatalog->value(newMD5+"/Players","").toString(); currentRom->saveType = romCatalog->value(newMD5+"/SaveType","").toString(); currentRom->rumble = romCatalog->value(newMD5+"/Rumble","").toString(); } if (!cached && SETTINGS.value("Other/downloadinfo", "").toString() == "true") { if (currentRom->goodName != getTranslation("Unknown ROM") && currentRom->goodName != getTranslation("Requires catalog file")) { scraper->downloadGameInfo(currentRom->romMD5, currentRom->goodName); } else { //tweak internal name by adding spaces to get better results QString search = currentRom->internalName; search.replace(QRegExp("([a-z])([A-Z])"),"\\1 \\2"); search.replace(QRegExp("([^ \\d])(\\d)"),"\\1 \\2"); scraper->downloadGameInfo(currentRom->romMD5, search); } } if (SETTINGS.value("Other/downloadinfo", "").toString() == "true") { QString dataFile = getCacheLocation() + currentRom->romMD5.toLower() + "/data.json"; QFile file(dataFile); file.open(QIODevice::ReadOnly); QString data = file.readAll(); file.close(); QJsonDocument document = QJsonDocument::fromJson(data.toUtf8()); QJsonObject json = document.object(); //Remove any non-standard characters QString regex = "[^A-Za-z 0-9 \\.,\\?'""!@#\\$%\\^&\\*\\(\\)-_=\\+;:<>\\/\\\\|\\}\\{\\[\\]`~é]*"; currentRom->gameTitle = json.value("game_title").toString().remove(QRegExp(regex)); if (currentRom->gameTitle == "") currentRom->gameTitle = getTranslation("Not found"); currentRom->releaseDate = json.value("release_date").toString(); currentRom->sortDate = json.value("release_date").toString(); currentRom->releaseDate.replace(QRegExp("(\\d{4})-(\\d{2})-(\\d{2})"), "\\2/\\3/\\1"); currentRom->overview = json.value("overview").toString().remove(QRegExp(regex)); currentRom->esrb = json.value("rating").toString(); currentRom->genre = json.value("genres").toString(); currentRom->publisher = json.value("publisher").toString(); currentRom->developer = json.value("developer").toString(); foreach (QString ext, QStringList() << "jpg" << "png") { QString imageFile = getCacheLocation() + currentRom->romMD5.toLower() + "/boxart-front." + ext; QFile cover(imageFile); if (cover.exists() && currentRom->image.load(imageFile)) { currentRom->imageExists = true; break; } } } } QStringList RomCollection::scanDirectory(QDir romDir) { QStringList files = romDir.entryList(fileTypes, QDir::Files | QDir::NoSymLinks); QStringList dirs = romDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); foreach (QString dir, dirs) { QString subDir = romDir.absolutePath() + "/" + dir; QStringList subFiles = QDir(subDir).entryList(fileTypes, QDir::Files | QDir::NoSymLinks); foreach (QString subFile, subFiles) files << dir + "/" + subFile; } return files; } void RomCollection::setupDatabase() { // Bump this when updating rom_collection structure // Will cause clients to delete and recreate the table int dbVersion = 2; database = QSqlDatabase::addDatabase("QSQLITE"); database.setDatabaseName(getDataLocation() + "/"+AppNameLower+".sqlite"); if (!database.open()) QMessageBox::warning(parent, tr("Database Not Loaded"), tr("Could not connect to Sqlite database. Application may misbehave.")); QSqlQuery version = database.exec("PRAGMA user_version"); version.next(); if (version.value(0).toInt() != dbVersion) { //old database version, reset rom_collection version.finish(); database.exec("DROP TABLE rom_collection"); database.exec("PRAGMA user_version = " + QString::number(dbVersion)); } database.exec(QString() + "CREATE TABLE IF NOT EXISTS rom_collection (" + "rom_id INTEGER PRIMARY KEY ASC, " + "filename TEXT NOT NULL, " + "directory TEXT NOT NULL, " + "md5 TEXT NOT NULL, " + "internal_name TEXT, " + "zip_file TEXT, " + "size INTEGER, " + "dd_rom INTEGER)"); database.close(); } void RomCollection::setupProgressDialog(int size) { progress = new QProgressDialog(tr("Loading ROMs..."), tr("Cancel"), 0, size, parent); progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowCloseButtonHint); progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowMinimizeButtonHint); progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint); progress->setCancelButton(nullptr); progress->setWindowModality(Qt::WindowModal); progress->show(); } void RomCollection::updatePaths(QStringList romPaths) { this->romPaths = romPaths; this->romPaths.removeAll(""); } ./src/roms/thegamesdbscraper.cpp0000664000175000017500000004162613623376647015273 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "thegamesdbscraper.h" #include "../global.h" #include "../common.h" #include #include #include #include #include #include #include #include #include #include #include #include TheGamesDBScraper::TheGamesDBScraper(QWidget *parent, bool force) : QObject(parent) { this->parent = parent; this->force = force; this->keepGoing = true; } QString TheGamesDBScraper::convertIDs(QJsonObject foundGame, QString typeName, QString listName) { QJsonArray idArray = foundGame.value(typeName).toArray(); QString cacheFileString = getCacheLocation() + typeName + ".json"; QFile cacheFile(cacheFileString); cacheFile.open(QIODevice::ReadOnly); QString data = cacheFile.readAll(); cacheFile.close(); QJsonDocument document = QJsonDocument::fromJson(data.toUtf8()); QJsonObject cache = document.object(); QString result = ""; foreach (QJsonValue id, idArray) { QString entryID = QString::number(id.toInt()); QString entryName = cache.value(entryID).toObject().value("name").toString(); if (entryName == "") { updateListCache(&cacheFile, listName); cacheFile.open(QIODevice::ReadOnly); data = cacheFile.readAll(); cacheFile.close(); document = QJsonDocument::fromJson(data.toUtf8()); entryName = cache.value(entryID).toObject().value("name").toString(); } if (entryName != "") result += entryName + ", "; } int pos = result.lastIndexOf(QChar(',')); return result.left(pos); } void TheGamesDBScraper::deleteGameInfo(QString fileName, QString identifier) { QString text; text = QString(tr("NOTE: If you are deleting this game's information because the game doesn't ")) + tr("exist on TheGamesDB and pulled the information for different game, it's ") + tr("better to create an account on")+" TheGamesDB " + tr("and add the game so other users can benefit as well.") + "

" + tr("This will cause to not update the information for this game until you ") + tr("force it with \"Download/Update Info...\"") + "

" + tr("Delete the current information for") + " " + fileName + "?"; text.replace("",AppName); int answer = QMessageBox::question(parent, tr("Delete Game Information"), text, QMessageBox::Yes | QMessageBox::No); if (answer == QMessageBox::Yes) { QString gameCache = getCacheLocation() + identifier.toLower(); QString dataFile = gameCache + "/data.xml"; QFile file(dataFile); // Remove game information file.open(QIODevice::WriteOnly); QTextStream stream(&file); stream << "NULL"; file.close(); // Remove cover image QString coverFile = gameCache + "/boxart-front."; QFile coverJPG(coverFile + "jpg"); QFile coverPNG(coverFile + "png"); if (coverJPG.exists()) coverJPG.remove(); if (coverPNG.exists()) coverPNG.remove(); coverJPG.open(QIODevice::WriteOnly); QTextStream streamImage(&coverJPG); streamImage << ""; coverJPG.close(); } } void TheGamesDBScraper::downloadGameInfo(QString identifier, QString searchName, QString gameID) { if (keepGoing && identifier != "") { if (force) parent->setEnabled(false); bool updated = false; QString gameCache = getCacheLocation() + identifier.toLower(); QDir cache(gameCache); if (!cache.exists()) { cache.mkpath(gameCache); } QFile genres(getCacheLocation() + "genres.json"); if (!genres.exists()) updateListCache(&genres, "Genres"); QFile developers(getCacheLocation() + "developers.json"); if (!developers.exists()) updateListCache(&developers, "Developers"); QFile publishers(getCacheLocation() + "publishers.json"); if (!publishers.exists()) updateListCache(&publishers, "Publishers"); //Get game JSON info from thegamesdb.net QString dataFile = gameCache + "/data.json"; QFile file(dataFile); if (!file.exists() || file.size() == 0 || force) { QUrl url; //Remove [!], (U), etc. from GoodName for searching searchName.remove(QRegExp("\\W*(\\(|\\[).+(\\)|\\])\\W*")); //Few game specific hacks if (searchName == "Legend of Zelda, The - Majora's Mask" || searchName == "ZELDA MAJORA'S MASK") searchName = "Majora's Mask"; else if (searchName == "Legend of Zelda, The - Ocarina of Time" || searchName == "THE LEGEND OF ZELDA") searchName = "The Legend of Zelda: Ocarina of Time"; else if (searchName.toLower().startsWith("tsumi to batsu")) searchName = "Sin and Punishment"; else if (searchName.toLower() == "1080 snowboarding") searchName = "1080: TenEighty Snowboarding"; else if (searchName == "Extreme-G XG2" || searchName == "Extreme G 2") searchName = "Extreme-G 2"; else if (searchName.contains("Pokemon", Qt::CaseInsensitive)) searchName.replace("Pokemon", "Pokémon", Qt::CaseInsensitive); else if (searchName.toLower() == "smash brothers") searchName = "Super Smash Bros."; else if (searchName.toLower() == "conker bfd") searchName = "Conker's Bad Fur Day"; QString apiFilter = "&filter[platform]=3&include=boxart&fields=game_title,release_date,"; apiFilter += "developers,publishers,genres,overview,rating,players"; QString apiURL = SETTINGS.value("TheGamesDB/url", "https://api.thegamesdb.net/").toString(); QString gameIDPrefix = SETTINGS.value("TheGamesDB/ByGameID", "/v1/Games/ByGameID").toString(); QString gameNamePrefix = SETTINGS.value("TheGamesDB/ByGameName", "/v1.1/Games/ByGameName").toString(); //If user submits gameID, use that if (gameID != "") url.setUrl(apiURL + gameIDPrefix + "?apikey=" + TheGamesDBAPIKey + "&id=" + gameID + apiFilter); else url.setUrl(apiURL + gameNamePrefix + "?apikey=" + TheGamesDBAPIKey + "&name=" + searchName + apiFilter); QString data = getUrlContents(url); QJsonDocument document = QJsonDocument::fromJson(data.toUtf8()); QJsonObject json = document.object(); if (json.value("code").toInt() != 200 && json.value("code").toInt() != 0) { QString status = json.value("status").toString(); QString message; message = QString(tr("The following error from TheGamesDB occured while downloading:")) + "

" + status + "

"; showError(message); if (force) parent->setEnabled(true); return; } QJsonValue games = json.value("data").toObject().value("games"); QJsonArray gamesArray = games.toArray(); int count = 0, found = 0; foreach (QJsonValue game, gamesArray) { QJsonValue title = game.toObject().value("game_title"); if (force) { //from user dialog QJsonValue date = game.toObject().value("release_date"); QString check = "Game: " + title.toString(); check.remove(QRegExp(QString("[^A-Za-z 0-9 \\.,\\?'""!@#\\$%\\^&\\*\\") + "(\\)-_=\\+;:<>\\/\\\\|\\}\\{\\[\\]`~é]*")); if (date.toString() != "") check += "\n" + tr("Released on: ") + date.toString(); check += "\n\n" + tr("Does this look correct?"); int answer = QMessageBox::question(parent, QObject::tr("Game Information Download"), check, QMessageBox::Yes | QMessageBox::No); if (answer == QMessageBox::Yes) { found = count; updated = true; break; } } else { //We only want one game, so search for a perfect match in the GameTitle element. //Otherwise this will default to 0 (the first game found) if(title.toString().toLower() == searchName.toLower()) found = count; } count++; } if (!force || updated) { QJsonObject foundGame = gamesArray.at(found).toObject(); QJsonObject saveData; QString gameID = QString::number(foundGame.value("id").toInt()); QJsonObject boxart = json.value("include").toObject().value("boxart").toObject(); QString thumbURL = boxart.value("base_url").toObject().value("thumb").toString(); QJsonArray imgArray = boxart.value("data").toObject().value(gameID).toArray(); QString frontImg = ""; foreach (QJsonValue img, imgArray) { QString type = img.toObject().value("type").toString(); QString side = img.toObject().value("side").toString(); QString filename = img.toObject().value("filename").toString(); if (type == "boxart" && side == "front") frontImg = thumbURL + filename; } //Convert IDs from API to text names QString genresString = convertIDs(foundGame, "genres", "Genres"); QString developerString = convertIDs(foundGame, "developers", "Developers"); QString publisherString = convertIDs(foundGame, "publishers", "Publishers"); QString players = QString::number(foundGame.value("players").toInt()); if (players == "0") players = ""; saveData.insert("game_title", foundGame.value("game_title").toString()); saveData.insert("release_date", foundGame.value("release_date").toString()); saveData.insert("rating", foundGame.value("rating").toString()); saveData.insert("overview", foundGame.value("overview").toString()); saveData.insert("players", players); saveData.insert("boxart", frontImg); saveData.insert("genres", genresString); saveData.insert("developer", developerString); saveData.insert("publisher", publisherString); QJsonDocument document(saveData); file.open(QIODevice::WriteOnly); file.write(document.toJson()); file.close(); } if (force && !updated) { QString message; if (count == 0) message = QObject::tr("No results found."); else message = QObject::tr("No more results found."); QMessageBox::information(parent, QObject::tr("Game Information Download"), message); } } //Get front cover QString boxartURL = ""; QString boxartExt = ""; QString coverFile = gameCache + "/boxart-front."; QFile coverJPG(coverFile + "jpg"); QFile coverPNG(coverFile + "png"); if ((!coverJPG.exists() && !coverPNG.exists()) || (force && updated)) { file.open(QIODevice::ReadOnly); QString data = file.readAll(); file.close(); QJsonDocument document = QJsonDocument::fromJson(data.toUtf8()); QJsonObject json = document.object(); QString boxartURL = json.value("boxart").toString(); if (boxartURL != "") { QUrl url(boxartURL); //Delete current box art QFile::remove(coverFile + "jpg"); QFile::remove(coverFile + "png"); //Check to save as JPG or PNG boxartExt = QFileInfo(boxartURL).completeSuffix().toLower(); QFile cover(coverFile + boxartExt); cover.open(QIODevice::WriteOnly); cover.write(getUrlContents(url)); cover.close(); } } if (updated) QMessageBox::information(parent, QObject::tr("Game Information Download"), QObject::tr("Download Complete!")); if (force) parent->setEnabled(true); } } QByteArray TheGamesDBScraper::getUrlContents(QUrl url) { QNetworkAccessManager *manager = new QNetworkAccessManager; QNetworkRequest request; request.setUrl(url); request.setRawHeader("User-Agent", AppName.toUtf8().constData()); QNetworkReply *reply = manager->get(request); QTimer timer; timer.setSingleShot(true); QEventLoop loop; connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); int time = SETTINGS.value("Other/networktimeout", 10).toInt(); if (time == 0) time = 10; time *= 1000; timer.start(time); loop.exec(); if(timer.isActive()) { //Got reply timer.stop(); if(reply->error() > 0) showError(reply->errorString()); else return reply->readAll(); } else //Request timed out showError(tr("Request timed out. Check your network settings.")); return QByteArray(); } void TheGamesDBScraper::showError(QString error) { QString question = "\n\n" + tr("Continue scraping information?"); if (force) QMessageBox::information(parent, tr("Network Error"), error); else { int answer = QMessageBox::question(parent, tr("Network Error"), error + question, QMessageBox::Yes | QMessageBox::No); if (answer == QMessageBox::No) keepGoing = false; } } void TheGamesDBScraper::updateListCache(QFile *file, QString list) { if (keepGoing) { QUrl url; QString apiURL = SETTINGS.value("TheGamesDB/url", "https://api.thegamesdb.net/").toString(); QString prefix; if (list == "Genres") prefix = SETTINGS.value("TheGamesDB/Genres", "/v1/Genres").toString(); else if (list == "Developers") prefix = SETTINGS.value("TheGamesDB/Developers", "/v1/Developers").toString(); else if (list == "Publishers") prefix = SETTINGS.value("TheGamesDB/Publishers", "/v1/Publishers").toString(); url.setUrl(apiURL + prefix + "?apikey=" + TheGamesDBAPIKey); QString data = getUrlContents(url); QJsonDocument document = QJsonDocument::fromJson(data.toUtf8()); QJsonDocument result(document.object().value("data").toObject().value(list.toLower()).toObject()); file->open(QIODevice::WriteOnly); file->write(result.toJson()); file->close(); } } ./src/roms/thegamesdbscraper.h0000664000175000017500000000436613623376647014740 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef THEGAMESDBSCRAPER_H #define THEGAMESDBSCRAPER_H #include #include class QUrl; class TheGamesDBScraper : public QObject { Q_OBJECT public: explicit TheGamesDBScraper(QWidget *parent = 0, bool force = false); void deleteGameInfo(QString fileName, QString identifier); void downloadGameInfo(QString identifier, QString searchName, QString gameID = ""); private: QString convertIDs(QJsonObject foundGame, QString typeName, QString listName); QByteArray getUrlContents(QUrl url); void showError(QString error); void updateListCache(QFile *file, QString list); bool force; bool keepGoing; QWidget *parent; }; #endif // THEGAMESDBSCRAPER_H ./src/roms/romcollection.h0000664000175000017500000000544713623376647014127 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef ROMCOLLECTION_H #define ROMCOLLECTION_H #include #include #include class QDir; class QProgressDialog; class TheGamesDBScraper; struct Rom; class RomCollection : public QObject { Q_OBJECT public: explicit RomCollection(QStringList fileTypes, QStringList romPaths, QWidget *parent = 0); int cachedRoms(bool imageUpdated = false, bool onStartup = false); void updatePaths(QStringList romPaths); QStringList getFileTypes(bool archives = false); QStringList romPaths; public slots: int addRoms(); signals: void ddRomAdded(Rom *currentRom); void romAdded(Rom *currentRom, int count); void updateEnded(int romCount, bool cached = false); void updateStarted(bool imageUpdated = false); private: void initializeRom(Rom *currentRom, bool cached); void setupDatabase(); void setupProgressDialog(int size); Rom addRom(QByteArray *romData, QString fileName, QString directory, QString zipFile, QSqlQuery query, bool ddRom = false); QStringList fileTypes; QStringList scanDirectory(QDir romDir); QWidget *parent; QProgressDialog *progress; QSqlDatabase database; TheGamesDBScraper *scraper; }; #endif // ROMCOLLECTION_H ./src/mainwindow.h0000664000175000017500000001132713623376647012444 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include class QActionGroup; class QDialogButtonBox; class QDir; class QHeaderView; class QGridLayout; class QLabel; class QListWidget; class QMenuBar; class QScrollArea; class QSplitter; class QStatusBar; class QTreeWidget; class QVBoxLayout; class DDView; class EmulatorHandler; class GridView; class ListView; class RomCollection; class TableView; class TheGamesDBScraper; class TreeWidgetItem; struct Rom; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); protected: void closeEvent(QCloseEvent *event); bool eventFilter(QObject*, QEvent *event); private: void createMenu(); void createRomView(); void launchRom(QDir romDir, QString romFileName, QString zipFileName); void openZipDialog(QStringList zippedFiles); void resetLayouts(bool imageUpdated = false); void restoreSplitterSize(); void showActiveView(); QString getCurrentRomInfoFromView(QString infoName); QString openPath; QAction *aboutAction; QAction *configureAction; QAction *convertAction; QAction *deleteAction; QAction *downloadAction; QAction *fullScreenAction; QAction *logAction; QAction *ddAction; QAction *openAction; QAction *quitAction; QAction *refreshAction; QAction *startAction; QAction *statusBarAction; QAction *stopAction; QActionGroup *layoutGroup; QDialog *zipDialog; QDialogButtonBox *zipButtonBox; QGridLayout *emptyLayout; QGridLayout *zipLayout; QHeaderView *ddHeaderView; QLabel *emptyIcon; QLabel *disabledLabel; QList sizeInts; QList menuEnable; QList menuDisable; QList menuRomSelected; QListWidget *zipList; QMenu *emulationMenu; QMenu *fileMenu; QMenu *helpMenu; QMenu *layoutMenu; QMenu *settingsMenu; QMenu *viewMenu; QMenuBar *menuBar; QScrollArea *emptyView; QSplitter *viewSplitter; QStatusBar *statusBar; QVBoxLayout *disabledLayout; QVBoxLayout *mainLayout; QWidget *disabledView; QWidget *mainWidget; EmulatorHandler *emulation; DDView *ddView; GridView *gridView; ListView *listView; RomCollection *romCollection; TableView *tableView; TheGamesDBScraper *scraper; TreeWidgetItem *fileItem; private slots: void addToView(Rom *currentRom, int count); void disableButtons(); void disableViews(bool imageUpdated); void enableButtons(); void enableViews(int romCount, bool cached); void launchRomFromMenu(); void launchRomFromTable(); void launchRomFromWidget(QWidget *current); void launchRomFromZip(); void openAbout(); void openConverter(); void openDeleteDialog(); void openDownloader(); void openLog(); void openSettings(); void openRom(); void showMenuBar(bool mouseAtTop); void showRomMenu(const QPoint &); void stopEmulator(); void toggleMenus(bool active); void update64DD(); void updateFullScreenMode(); void updateLayoutSetting(); void updateStatusBar(QString message, int timeout); void updateStatusBarView(); }; #endif // MAINWINDOW_H ./src/global.h0000664000175000017500000000441313623376647011526 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef GLOBAL_H #define GLOBAL_H #include #include const QString AppName = "CEN64-Qt"; const QString AppNameLower = "cen64-qt"; const QString ParentName = "CEN64"; const QString ParentNameLower = "cen64"; const QString Copyright = "CEN64 Copyright (c) 2013, Tyler J. Stachecki"; const QString Website = "http://www.cen64.com/"; const QString GitHub = "https://github.com/dh4/cen64-qt"; const QString TheGamesDBAPIKey = "750dbe45c2f1c63dd3a730b0ef0923ec448b543a908fbe3e2bccada4341df2f5"; #ifdef Q_OS_WIN #define SETTINGS QSettings(AppNameLower+".ini", QSettings::IniFormat) #else #define SETTINGS QSettings(ParentNameLower, AppNameLower) #endif #endif // GLOBAL_H ./src/common.cpp0000664000175000017500000003425413623376647012117 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "common.h" #include "global.h" #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #else #include #endif QByteArray byteswap(QByteArray romData) { QByteArray flipped; if (romData.left(4).toHex() == "37804012") { for (int i = 0; i < romData.length(); i += 2) { flipped.append(romData[i + 1]); flipped.append(romData[i]); } return flipped; } else { return romData; } } QString getCacheLocation() { return getDataLocation() + "/cache_v2/"; } QString getDataLocation() { QString dataDir; #ifdef Q_OS_WIN dataDir = QCoreApplication::applicationDirPath(); #else dataDir = QStandardPaths::writableLocation(QStandardPaths::DataLocation) .replace(ParentName+"/"+AppName,AppNameLower); #endif QDir data(dataDir); if (!data.exists()) data.mkpath(dataDir); return dataDir; } QColor getColor(QString color, int transparency) { if (transparency <= 255) { if (color == "Black") return QColor(0, 0, 0, transparency); else if (color == "White") return QColor(255, 255, 255, transparency); else if (color == "Light Gray") return QColor(200, 200, 200, transparency); else if (color == "Dark Gray") return QColor(50, 50, 59, transparency); else if (color == "Green") return QColor(0, 255, 0, transparency); else if (color == "Cyan") return QColor(30, 175, 255, transparency); else if (color == "Blue") return QColor(0, 0, 255, transparency); else if (color == "Purple") return QColor(128, 0, 128, transparency); else if (color == "Red") return QColor(255, 0, 0, transparency); else if (color == "Pink") return QColor(246, 96, 171, transparency); else if (color == "Orange") return QColor(255, 165, 0, transparency); else if (color == "Yellow") return QColor(255, 255, 0, transparency); else if (color == "Brown") return QColor(127, 70, 44, transparency); } return QColor(0, 0, 0, 255); } QString getDefaultLanguage() { QString systemLanguage = QLocale::system().name().left(2); //Add other languages here as translations are done if (systemLanguage == "fr") return "FR"; else if (systemLanguage == "ru") return "RU"; else return "EN"; } int getDefaultWidth(QString id, int imageWidth) { if (id == "Overview") return 400; else if (id == "GoodName" || id.left(8) == "Filename" || id == "Game Title") return 300; else if (id == "MD5") return 250; else if (id == "Internal Name" || id == "Publisher" || id == "Developer") return 200; else if (id == "ESRB" || id == "Genre") return 150; else if (id == "Save Type" || id == "Release Date") return 100; else if (id == "CRC1" || id == "CRC2") return 90; else if (id == "Size" || id == "Rumble" || id == "Players" || id == "Rating") return 75; else if (id == "Game Cover") return imageWidth; else return 100; } int getGridSize(QString which) { QString size = SETTINGS.value("Grid/imagesize","Medium").toString(); if (which == "height") { if (SETTINGS.value("Grid/label", "true").toString() == "true") { if (size == "Extra Small") return 65; if (size == "Small") return 90; if (size == "Medium") return 145; if (size == "Large") return 190; if (size == "Extra Large") return 250; if (size == "Super") return 360; } else { if (size == "Extra Small") return 47; if (size == "Small") return 71; if (size == "Medium") return 122; if (size == "Large") return 172; if (size == "Extra Large") return 224; if (size == "Super") return 330; } } else if (which == "width") { if (size == "Extra Small") return 60; if (size == "Small") return 90; if (size == "Medium") return 160; if (size == "Large") return 225; if (size == "Extra Large") return 300; if (size == "Super") return 440; } else if (which == "font") { if (size == "Extra Small") return 5; if (size == "Small") return 7; if (size == "Medium") return 10; if (size == "Large") return 12; if (size == "Extra Large") return 13; if (size == "Super") return 15; } return 0; } QSize getImageSize(QString view) { QString size = SETTINGS.value(view+"/imagesize","Medium").toString(); if (view == "Table") { if (size == "Extra Small") return QSize(33, 24); if (size == "Small") return QSize(48, 35); if (size == "Medium") return QSize(69, 50); if (size == "Large") return QSize(103, 75); if (size == "Extra Large") return QSize(138, 100); if (size == "Super") return QSize(210, 150); } else if (view == "Grid" || view == "List") { if (size == "Extra Small") return QSize(48, 35); if (size == "Small") return QSize(69, 50); if (size == "Medium") return QSize(138, 100); if (size == "Large") return QSize(203, 150); if (size == "Extra Large") return QSize(276, 200); if (size == "Super") return QSize(425, 300); } return QSize(); } QString getRomInfo(QString identifier, const Rom *rom, bool removeWarn, bool sort) { QString text = ""; if (identifier == "GoodName") text = rom->goodName; else if (identifier == "Filename") text = rom->baseName; else if (identifier == "Filename (extension)") text = rom->fileName; else if (identifier == "Zip File") text = rom->zipFile; else if (identifier == "Internal Name") text = rom->internalName; else if (identifier == "Size") text = rom->size; else if (identifier == "MD5") text = rom->romMD5.toLower(); else if (identifier == "CRC1") text = rom->CRC1.toLower(); else if (identifier == "CRC2") text = rom->CRC2.toLower(); else if (identifier == "Players") text = rom->players; else if (identifier == "Rumble") text = rom->rumble; else if (identifier == "Save Type") text = rom->saveType; else if (identifier == "Game Title") text = rom->gameTitle; else if (identifier == "Release Date") text = rom->releaseDate; else if (identifier == "Overview") text = rom->overview; else if (identifier == "ESRB") text = rom->esrb; else if (identifier == "Genre") text = rom->genre; else if (identifier == "Publisher") text = rom->publisher; else if (identifier == "Developer") text = rom->developer; else if (identifier == "Rating") text = rom->rating; if (!removeWarn) return text; else if (text == getTranslation("Unknown ROM") || text == getTranslation("Requires catalog file") || text == getTranslation("Not found")) { if (sort) return "ZZZ"; //Sort warnings at the end else return ""; } else return text; } QGraphicsDropShadowEffect *getShadow(bool active) { QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect; if (active) { shadow->setBlurRadius(25.0); shadow->setColor(getColor(SETTINGS.value("Grid/activecolor","Cyan").toString(), 255)); shadow->setOffset(0); } else { shadow->setBlurRadius(10.0); shadow->setColor(getColor(SETTINGS.value("Grid/inactivecolor","Black").toString(), 200)); shadow->setOffset(0); } return shadow; } int getTableDataIndexFromName(QString infoName) { if (infoName == "fileName") return 0; else if (infoName == "dirName") return 1; else if (infoName == "search") return 2; else if (infoName == "romMD5") return 3; else if (infoName == "zipFile") return 4; return 0; } int getTextSize() { QString size = SETTINGS.value("List/textsize","Medium").toString(); if (size == "Extra Small") return 7; if (size == "Small") return 9; if (size == "Medium") return 10; if (size == "Large") return 12; if (size == "Extra Large") return 14; return 10; } QString getTranslation(QString text) { if (text == "GoodName") return QObject::tr("GoodName"); else if (text == "Filename") return QObject::tr("Filename"); else if (text == "Filename (extension)") return QObject::tr("Filename (extension)"); else if (text == "Zip File") return QObject::tr("Zip File"); else if (text == "Internal Name") return QObject::tr("Internal Name"); else if (text == "Size") return QObject::tr("Size"); else if (text == "MD5") return QObject::tr("MD5"); else if (text == "CRC1") return QObject::tr("CRC1"); else if (text == "CRC2") return QObject::tr("CRC2"); else if (text == "Players") return QObject::tr("Players"); else if (text == "Rumble") return QObject::tr("Rumble"); else if (text == "Save Type") return QObject::tr("Save Type"); else if (text == "Game Title") return QObject::tr("Game Title"); else if (text == "Release Date") return QObject::tr("Release Date"); else if (text == "Overview") return QObject::tr("Overview"); else if (text == "ESRB") return QObject::tr("ESRB"); else if (text == "Genre") return QObject::tr("Genre"); else if (text == "Publisher") return QObject::tr("Publisher"); else if (text == "Developer") return QObject::tr("Developer"); else if (text == "Rating") return QObject::tr("Rating"); else if (text == "Game Cover") return QObject::tr("Game Cover"); else if (text == "Unknown ROM") return QObject::tr("Unknown ROM"); else if (text == "Requires catalog file") return QObject::tr("Requires catalog file"); else if (text == "Not found") return QObject::tr("Not found"); return text; } QString getVersion() { QFile versionFile(":/other/VERSION"); versionFile.open(QIODevice::ReadOnly); QString version = versionFile.readAll(); versionFile.close(); return version; } QStringList getZippedFiles(QString completeFileName) { QuaZip zipFile(completeFileName); zipFile.open(QuaZip::mdUnzip); QStringList files = zipFile.getFileNameList(); zipFile.close(); return files; } QByteArray *getZippedRom(QString romFileName, QString zipFile) { QuaZipFile zippedFile(zipFile, romFileName); zippedFile.open(QIODevice::ReadOnly); QByteArray *romData = new QByteArray(); romData->append(zippedFile.readAll()); zippedFile.close(); return romData; } bool romSorter(const Rom &firstRom, const Rom &lastRom) { QString sort, direction; QString layout = SETTINGS.value("View/layout", "None").toString(); if (layout == "grid") { sort = SETTINGS.value("Grid/sort", "Filename").toString(); direction = SETTINGS.value("Grid/sortdirection", "ascending").toString(); } else if (layout == "list") { sort = SETTINGS.value("List/sort", "Filename").toString(); direction = SETTINGS.value("List/sortdirection", "ascending").toString(); } else //just return sort by filename return firstRom.fileName < lastRom.fileName; QString sortFirst = "", sortLast = ""; if (sort == "Size") { int firstSize = firstRom.sortSize; int lastSize = lastRom.sortSize; if (direction == "descending") return firstSize > lastSize; else return firstSize < lastSize; } else if (sort == "Release Date") { sortFirst = firstRom.sortDate; sortLast = lastRom.sortDate; } else { sortFirst = getRomInfo(sort, &firstRom, true, true); sortLast = getRomInfo(sort, &lastRom, true, true); } if (sortFirst == sortLast) { //Equal so sort on filename sortFirst = firstRom.fileName; sortLast = lastRom.fileName; } if (direction == "descending") return sortFirst > sortLast; else return sortFirst < sortLast; } ./src/mainwindow.cpp0000664000175000017500000010154213623376647012776 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "mainwindow.h" #include "global.h" #include "common.h" #include "dialogs/aboutdialog.h" #include "dialogs/downloaddialog.h" #include "dialogs/logdialog.h" #include "dialogs/settingsdialog.h" #include "dialogs/v64converter.h" #include "emulation/emulatorhandler.h" #include "roms/romcollection.h" #include "roms/thegamesdbscraper.h" #include "views/gridview.h" #include "views/listview.h" #include "views/tableview.h" #include "views/ddview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setWindowTitle(AppName); setWindowIcon(QIcon(":/images/"+ParentNameLower+".png")); installEventFilter(this); emulation = new EmulatorHandler(this); romCollection = new RomCollection(QStringList() << "*.z64" << "*.n64" << "*.zip" << "*.ndd", QStringList() << SETTINGS.value("Paths/roms","").toString().split("|"), this); createMenu(); createRomView(); connect(emulation, SIGNAL(started()), this, SLOT(disableButtons())); connect(emulation, SIGNAL(finished()), this, SLOT(enableButtons())); connect(emulation, SIGNAL(showLog()), this, SLOT(openLog())); connect(emulation, SIGNAL(statusUpdate(QString, int)), this, SLOT(updateStatusBar(QString, int))); connect(romCollection, SIGNAL(updateStarted(bool)), this, SLOT(disableViews(bool))); connect(romCollection, SIGNAL(romAdded(Rom*, int)), this, SLOT(addToView(Rom*, int))); connect(romCollection, SIGNAL(ddRomAdded(Rom*)), ddView, SLOT(addTo64DDView(Rom*))); connect(romCollection, SIGNAL(updateEnded(int, bool)), this, SLOT(enableViews(int, bool))); romCollection->cachedRoms(false, true); mainWidget = new QWidget(this); setCentralWidget(mainWidget); setGeometry(QRect(SETTINGS.value("Geometry/windowx", 0).toInt(), SETTINGS.value("Geometry/windowy", 0).toInt(), SETTINGS.value("Geometry/width", 900).toInt(), SETTINGS.value("Geometry/height", 600).toInt())); statusBar = new QStatusBar; if (SETTINGS.value("View/statusbar", "").toString() == "") statusBar->hide(); if (SETTINGS.value("View/fullscreen", "").toString() == "true") updateFullScreenMode(); QString ddipl = SETTINGS.value("Paths/ddiplrom", "").toString(); QString ddEnabled = SETTINGS.value("Emulation/64dd", "").toString(); QString currentView = SETTINGS.value("View/layout", "none").toString(); if (ddipl != "" && ddEnabled == "true" && currentView != "none") ddView->setHidden(false); mainLayout = new QVBoxLayout(mainWidget); mainLayout->setMenuBar(menuBar); mainLayout->addWidget(viewSplitter); mainLayout->addWidget(statusBar); mainLayout->setMargin(0); mainWidget->setLayout(mainLayout); mainWidget->setMinimumSize(300, 200); } void MainWindow::addToView(Rom *currentRom, int count) { QString visibleLayout = SETTINGS.value("View/layout", "none").toString(); if (visibleLayout == "table") tableView->addToTableView(currentRom); else if (visibleLayout == "grid") gridView->addToGridView(currentRom, count, ddAction->isChecked()); else if (visibleLayout == "list") listView->addToListView(currentRom, count, ddAction->isChecked()); } void MainWindow::closeEvent(QCloseEvent *event) { SETTINGS.setValue("Geometry/windowx", geometry().x()); SETTINGS.setValue("Geometry/windowy", geometry().y()); SETTINGS.setValue("Geometry/width", geometry().width()); SETTINGS.setValue("Geometry/height", geometry().height()); if (isMaximized()) SETTINGS.setValue("Geometry/maximized", true); else SETTINGS.setValue("Geometry/maximized", ""); tableView->saveColumnWidths(); QStringList sizes; foreach(int size, viewSplitter->sizes()) sizes << QString::number(size); SETTINGS.setValue("View/64ddsize", sizes.join("|")); event->accept(); } void MainWindow::createMenu() { menuBar = new QMenuBar(this); //File fileMenu = new QMenu(tr("&File"), this); openAction = fileMenu->addAction(tr("&Open ROM...")); fileMenu->addSeparator(); convertAction = fileMenu->addAction(tr("&Convert V64/n64...")); refreshAction = fileMenu->addAction(tr("&Refresh List")); downloadAction = fileMenu->addAction(tr("&Download/Update Info...")); deleteAction = fileMenu->addAction(tr("D&elete Current Info...")); #ifndef Q_OS_OSX //OSX does not show the quit action so the separator is unneeded fileMenu->addSeparator(); #endif quitAction = fileMenu->addAction(tr("&Quit")); openAction->setIcon(QIcon::fromTheme("document-open")); refreshAction->setIcon(QIcon::fromTheme("view-refresh")); quitAction->setIcon(QIcon::fromTheme("application-exit")); downloadAction->setEnabled(false); deleteAction->setEnabled(false); menuBar->addMenu(fileMenu); connect(openAction, SIGNAL(triggered()), this, SLOT(openRom())); connect(convertAction, SIGNAL(triggered()), this, SLOT(openConverter())); connect(refreshAction, SIGNAL(triggered()), romCollection, SLOT(addRoms())); connect(downloadAction, SIGNAL(triggered()), this, SLOT(openDownloader())); connect(deleteAction, SIGNAL(triggered()), this, SLOT(openDeleteDialog())); connect(quitAction, SIGNAL(triggered()), this, SLOT(close())); //Emulation emulationMenu = new QMenu(tr("&Emulation"), this); startAction = emulationMenu->addAction(tr("&Start")); stopAction = emulationMenu->addAction(tr("St&op")); emulationMenu->addSeparator(); ddAction = emulationMenu->addAction(tr("Enable 64DD")); emulationMenu->addSeparator(); logAction = emulationMenu->addAction(tr("View Log...")); startAction->setIcon(QIcon::fromTheme("media-playback-start")); stopAction->setIcon(QIcon::fromTheme("media-playback-stop")); startAction->setEnabled(false); stopAction->setEnabled(false); ddAction->setCheckable(true); if (SETTINGS.value("Paths/ddiplrom", "").toString() != "") { if (SETTINGS.value("Emulation/64dd", "").toString() == "true") ddAction->setChecked(true); } else ddAction->setEnabled(false); menuBar->addMenu(emulationMenu); connect(startAction, SIGNAL(triggered()), this, SLOT(launchRomFromMenu())); connect(stopAction, SIGNAL(triggered()), this, SLOT(stopEmulator())); connect(ddAction, SIGNAL(triggered()), this, SLOT(update64DD())); connect(logAction, SIGNAL(triggered()), this, SLOT(openLog())); //Settings settingsMenu = new QMenu(tr("&Settings"), this); configureAction = settingsMenu->addAction(tr("&Configure...")); configureAction->setIcon(QIcon::fromTheme("preferences-other")); menuBar->addMenu(settingsMenu); connect(configureAction, SIGNAL(triggered()), this, SLOT(openSettings())); //View viewMenu = new QMenu(tr("&View"), this); layoutMenu = viewMenu->addMenu(tr("&Layout")); layoutGroup = new QActionGroup(this); QList layouts; layouts << (QStringList() << tr("None") << "none") << (QStringList() << tr("Table View") << "table") << (QStringList() << tr("Grid View") << "grid") << (QStringList() << tr("List View") << "list"); QString layoutValue = SETTINGS.value("View/layout", "none").toString(); foreach (QStringList layoutName, layouts) { QAction *layoutItem = layoutMenu->addAction(layoutName.at(0)); layoutItem->setData(layoutName.at(1)); layoutItem->setCheckable(true); layoutGroup->addAction(layoutItem); //Only enable layout changes when emulator is not running menuEnable << layoutItem; if(layoutValue == layoutName.at(1)) layoutItem->setChecked(true); } viewMenu->addSeparator(); //OSX El Capitan adds it's own full-screen option #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) if (!(QOperatingSystemVersion::current() >= QOperatingSystemVersion::OSXElCapitan && QOperatingSystemVersion::currentType() == QOperatingSystemVersion::MacOS)) #else if (QSysInfo::macVersion() < QSysInfo::MV_ELCAPITAN || QSysInfo::macVersion() == QSysInfo::MV_None) #endif fullScreenAction = viewMenu->addAction(tr("&Full-screen")); else fullScreenAction = new QAction(this); statusBarAction = viewMenu->addAction(tr("&Status Bar")); fullScreenAction->setCheckable(true); statusBarAction->setCheckable(true); if (SETTINGS.value("View/fullscreen", "") == "true") fullScreenAction->setChecked(true); if (SETTINGS.value("View/statusbar", "") == "true") statusBarAction->setChecked(true); menuBar->addMenu(viewMenu); connect(fullScreenAction, SIGNAL(triggered()), this, SLOT(updateFullScreenMode())); connect(statusBarAction, SIGNAL(triggered()), this, SLOT(updateStatusBarView())); connect(layoutGroup, SIGNAL(triggered(QAction*)), this, SLOT(updateLayoutSetting())); //Help helpMenu = new QMenu(tr("&Help"), this); aboutAction = helpMenu->addAction(tr("&About")); aboutAction->setIcon(QIcon::fromTheme("help-about")); menuBar->addMenu(helpMenu); connect(aboutAction, SIGNAL(triggered()), this, SLOT(openAbout())); //Create list of actions that are enabled only when emulator is not running menuEnable << startAction << ddAction << logAction << openAction << convertAction << downloadAction << deleteAction << refreshAction << configureAction << quitAction; //Create list of actions that are disabled when emulator is not running menuDisable << stopAction; //Create list of actions that are only active when a ROM is selected menuRomSelected << startAction << deleteAction << downloadAction; } void MainWindow::createRomView() { //Create empty view emptyView = new QScrollArea(this); emptyView->setStyleSheet("QScrollArea { border: none; }"); emptyView->setBackgroundRole(QPalette::Base); emptyView->setAutoFillBackground(true); emptyView->setHidden(true); emptyLayout = new QGridLayout(emptyView); emptyIcon = new QLabel(emptyView); emptyIcon->setPixmap(QPixmap(":/images/"+ParentNameLower+".png")); emptyLayout->addWidget(emptyIcon, 1, 1); emptyLayout->setColumnStretch(0, 1); emptyLayout->setColumnStretch(2, 1); emptyLayout->setRowStretch(0, 1); emptyLayout->setRowStretch(2, 1); emptyView->setLayout(emptyLayout); //Create table view tableView = new TableView(this); connect(tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(enableButtons())); connect(tableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(launchRomFromTable())); connect(tableView, SIGNAL(tableActive()), this, SLOT(enableButtons())); connect(tableView, SIGNAL(enterPressed()), this, SLOT(launchRomFromTable())); //Create grid view gridView = new GridView(this); connect(gridView, SIGNAL(gridItemSelected(bool)), this, SLOT(toggleMenus(bool))); //Create list view listView = new ListView(this); connect(listView, SIGNAL(listItemSelected(bool)), this, SLOT(toggleMenus(bool))); //Create disabled view disabledView = new QWidget(this); disabledView->setHidden(true); disabledLayout = new QVBoxLayout(disabledView); disabledLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); disabledView->setLayout(disabledLayout); QString disabledText = QString(tr("Add a directory containing ROMs under ")) + tr("Settings->Configure->Paths to use this view."); disabledLabel = new QLabel(disabledText, disabledView); disabledLabel->setWordWrap(true); disabledLabel->setAlignment(Qt::AlignCenter); disabledLayout->addWidget(disabledLabel); //Create 64DD view ddView = new DDView(this); connect(ddView, SIGNAL(clicked(QModelIndex)), this, SLOT(enableButtons())); connect(ddView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(launchRomFromMenu())); //Put views within a splitter for 64DD view viewSplitter = new QSplitter(this); viewSplitter->setOrientation(Qt::Vertical); viewSplitter->addWidget(emptyView); viewSplitter->addWidget(tableView); viewSplitter->addWidget(gridView); viewSplitter->addWidget(listView); viewSplitter->addWidget(disabledView); viewSplitter->addWidget(ddView); //Restore 64DD panel size QStringList sizes = SETTINGS.value("View/64ddsize", "").toString().split("|"); foreach (QString size, sizes) sizeInts << size.toInt(); restoreSplitterSize(); showActiveView(); } void MainWindow::disableButtons() { toggleMenus(false); } void MainWindow::disableViews(bool imageUpdated) { QString visibleLayout = SETTINGS.value("View/layout", "none").toString(); //Save position in current layout if (visibleLayout == "table") tableView->saveTablePosition(); else if (visibleLayout == "grid") gridView->saveGridPosition(); else if (visibleLayout == "list") listView->saveListPosition(); resetLayouts(imageUpdated); tableView->clear(); ddView->clear(); if (ddAction->isChecked()) { //64DD enabled so show "No Cart" options Rom dummyRom; dummyRom.imageExists = false; tableView->addNoCartRow(); gridView->addToGridView(&dummyRom, -1, ddAction->isChecked()); listView->addToListView(&dummyRom, -1, ddAction->isChecked()); ddView->addNoDiskRow(); } tableView->setEnabled(false); gridView->setEnabled(false); listView->setEnabled(false); ddView->setEnabled(false); foreach (QAction *next, menuRomSelected) next->setEnabled(false); } void MainWindow::enableButtons() { toggleMenus(true); } void MainWindow::enableViews(int romCount, bool cached) { QString visibleLayout = SETTINGS.value("View/layout", "none").toString(); if (romCount != 0) { //else no ROMs, so leave views disabled QStringList tableVisible = SETTINGS.value("Table/columns", "Filename|Size").toString().split("|"); if (tableVisible.join("") != "") tableView->setEnabled(true); gridView->setEnabled(true); listView->setEnabled(true); ddView->setEnabled(true); if (visibleLayout == "table") tableView->setFocus(); else if (visibleLayout == "grid") gridView->setFocus(); else if (visibleLayout == "list") listView->setFocus(); //Check if disabled view is showing. If it is, re-enabled the selected view if (!disabledView->isHidden()) { disabledView->setHidden(true); showActiveView(); } if (cached) { QTimer *timer = new QTimer(this); timer->setSingleShot(true); timer->setInterval(0); timer->start(); if (visibleLayout == "table") connect(timer, SIGNAL(timeout()), tableView, SLOT(setTablePosition())); else if (visibleLayout == "grid") connect(timer, SIGNAL(timeout()), gridView, SLOT(setGridPosition())); else if (visibleLayout == "list") connect(timer, SIGNAL(timeout()), listView, SLOT(setListPosition())); } } else { if (visibleLayout != "none") { tableView->setHidden(true); gridView->setHidden(true); listView->setHidden(true); disabledView->setHidden(false); } } } bool MainWindow::eventFilter(QObject*, QEvent *event) { //Show menu bar if mouse is at top of screen in full-screen mode if (event->type() == QEvent::HoverMove && isFullScreen()) { QMouseEvent *mouseEvent = static_cast(event); int mousePos = mouseEvent->pos().y(); if (mousePos < 5) showMenuBar(true); if (mousePos > 30) showMenuBar(false); } if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); //Exit fullscreen mode if Esc key is pressed if (keyEvent->key() == Qt::Key_Escape && isFullScreen()) updateFullScreenMode(); } //OSX El Capitan adds it's own full-screen option, so handle the event change here #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::OSXElCapitan && QOperatingSystemVersion::currentType() == QOperatingSystemVersion::MacOS) { #else if (QSysInfo::macVersion() >= QSysInfo::MV_ELCAPITAN && QSysInfo::macVersion() != QSysInfo::MV_None) { #endif if (event->type() == QEvent::WindowStateChange) { QWindowStateChangeEvent *windowEvent = static_cast(event); if (windowEvent->oldState() == Qt::WindowNoState) { SETTINGS.setValue("View/fullscreen", true); tableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); gridView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { SETTINGS.setValue("View/fullscreen", ""); tableView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); gridView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); listView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } } } return false; } QString MainWindow::getCurrentRomInfoFromView(QString infoName) { QString visibleLayout = SETTINGS.value("View/layout", "none").toString(); if (visibleLayout == "table") return tableView->getCurrentRomInfo(infoName); else if (visibleLayout == "grid" && gridView->hasSelectedRom()) return gridView->getCurrentRomInfo(infoName); else if (visibleLayout == "list" && listView->hasSelectedRom()) return listView->getCurrentRomInfo(infoName); return ""; } void MainWindow::launchRom(QDir romDir, QString romFileName, QString zipFileName) { if (ddAction->isChecked() && ddView->hasSelectedRom()) { QString ddFileName = ddView->getCurrentRomInfo("fileName"); QString ddDirName = ddView->getCurrentRomInfo("dirName"); QString ddZipName = ddView->getCurrentRomInfo("zipFile"); emulation->startEmulator(romDir, romFileName, zipFileName, QDir(ddDirName), ddFileName, ddZipName); } else emulation->startEmulator(romDir, romFileName, zipFileName); } void MainWindow::launchRomFromMenu() { QString visibleLayout = layoutGroup->checkedAction()->data().toString(); if (visibleLayout == "table") launchRomFromTable(); else if (visibleLayout == "grid") launchRomFromWidget(gridView->getCurrentRomWidget()); else if (visibleLayout == "list") launchRomFromWidget(listView->getCurrentRomWidget()); } void MainWindow::launchRomFromTable() { if (tableView->hasSelectedRom()) { QString romFileName = tableView->getCurrentRomInfo("fileName"); QString romDirName = tableView->getCurrentRomInfo("dirName"); QString zipFileName = tableView->getCurrentRomInfo("zipFile"); launchRom(QDir(romDirName), romFileName, zipFileName); } else { launchRom(QDir(), "", ""); } } void MainWindow::launchRomFromWidget(QWidget *current) { QString romFileName = current->property("fileName").toString(); QString romDirName = current->property("directory").toString(); QString zipFileName = current->property("zipFile").toString(); launchRom(QDir(romDirName), romFileName, zipFileName); } void MainWindow::launchRomFromZip() { QString fileName = zipList->currentItem()->text(); zipDialog->close(); emulation->startEmulator(QDir(), fileName, openPath); } void MainWindow::openAbout() { AboutDialog aboutDialog(this); aboutDialog.exec(); } void MainWindow::openConverter() { V64Converter v64converter(romCollection->romPaths.at(0), this); } void MainWindow::openDeleteDialog() { scraper = new TheGamesDBScraper(this); scraper->deleteGameInfo(getCurrentRomInfoFromView("fileName"), getCurrentRomInfoFromView("romMD5")); delete scraper; romCollection->cachedRoms(); } void MainWindow::openDownloader() { DownloadDialog downloadDialog(getCurrentRomInfoFromView("fileName"), getCurrentRomInfoFromView("search"), getCurrentRomInfoFromView("romMD5"), this); downloadDialog.exec(); romCollection->cachedRoms(); } void MainWindow::openLog() { if (emulation->lastOutput == "") { QMessageBox::information(this, tr("No Output"), tr("There is no log. Either has not yet run or there was no output from the last run.") .replace("",ParentName)); } else { LogDialog logDialog(emulation->lastOutput, this); logDialog.exec(); } } void MainWindow::openSettings() { QString tableImageBefore = SETTINGS.value("Table/imagesize", "Medium").toString(); QString columnsBefore = SETTINGS.value("Table/columns", "Filename|Size").toString(); QString downloadBefore = SETTINGS.value("Other/downloadinfo", "").toString(); SettingsDialog settingsDialog(this, 0); settingsDialog.exec(); QString tableImageAfter = SETTINGS.value("Table/imagesize", "Medium").toString(); QString columnsAfter = SETTINGS.value("Table/columns", "Filename|Size").toString(); QString downloadAfter = SETTINGS.value("Other/downloadinfo", "").toString(); //Reset columns widths if user has selected different columns to display if (columnsBefore != columnsAfter) { SETTINGS.setValue("Table/width", ""); tableView->setColumnCount(3); tableView->setHeaderLabels(QStringList("")); } QStringList romSave = SETTINGS.value("Paths/roms","").toString().split("|"); if (romCollection->romPaths != romSave) { romCollection->updatePaths(romSave); romCollection->addRoms(); } else if (downloadBefore == "" && downloadAfter == "true") { romCollection->addRoms(); } else { if (tableImageBefore != tableImageAfter) romCollection->cachedRoms(true); else romCollection->cachedRoms(false); } if (SETTINGS.value("Paths/ddiplrom", "").toString() != "") { ddAction->setEnabled(true); } else { ddAction->setEnabled(false); ddAction->setChecked(false); update64DD(); } gridView->setGridBackground(); listView->setListBackground(); toggleMenus(true); } void MainWindow::openRom() { QString filter = "N64 ROMs ("; foreach (QString type, romCollection->getFileTypes(true)) filter += type + " "; filter += ");;" + tr("All Files") + " (*)"; QString searchPath = QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first(); if (romCollection->romPaths.count() > 0) searchPath = romCollection->romPaths.at(0); openPath = QFileDialog::getOpenFileName(this, tr("Open ROM File"), searchPath, filter); if (openPath != "") { if (QFileInfo(openPath).suffix() == "zip") { QStringList zippedFiles = getZippedFiles(openPath); QString last; int count = 0; foreach (QString file, zippedFiles) { QString ext = file.right(4).toLower(); if (romCollection->getFileTypes().contains("*" + ext)) { last = file; count++; } } if (count == 0) QMessageBox::information(this, tr("No ROMs"), tr("No ROMs found in ZIP file.")); else if (count == 1) emulation->startEmulator(QDir(QFileInfo(openPath).dir()), last, openPath); else { //More than one ROM in zip file, so let user select openZipDialog(zippedFiles); } } else emulation->startEmulator(QDir(QFileInfo(openPath).dir()), openPath); } } void MainWindow::openZipDialog(QStringList zippedFiles) { zipDialog = new QDialog(this); zipDialog->setWindowTitle(tr("Select ROM")); zipDialog->setMinimumSize(200, 150); zipDialog->resize(300, 150); zipLayout = new QGridLayout(zipDialog); zipLayout->setContentsMargins(5, 10, 5, 10); zipList = new QListWidget(zipDialog); foreach (QString file, zippedFiles) { QString ext = file.right(4); if (romCollection->getFileTypes().contains("*" + ext)) zipList->addItem(file); } zipList->setCurrentRow(0); zipButtonBox = new QDialogButtonBox(Qt::Horizontal, zipDialog); zipButtonBox->addButton(tr("Launch"), QDialogButtonBox::AcceptRole); zipButtonBox->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); zipLayout->addWidget(zipList, 0, 0); zipLayout->addWidget(zipButtonBox, 1, 0); connect(zipList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(launchRomFromZip())); connect(zipButtonBox, SIGNAL(accepted()), this, SLOT(launchRomFromZip())); connect(zipButtonBox, SIGNAL(rejected()), zipDialog, SLOT(close())); zipDialog->setLayout(zipLayout); zipDialog->exec(); } void MainWindow::resetLayouts(bool imageUpdated) { tableView->resetView(imageUpdated); gridView->resetView(); listView->resetView(); } void MainWindow::restoreSplitterSize() { int mainSize = 1, ddSize = 1; if (!sizeInts.isEmpty()) { ddSize = sizeInts.last(); sizeInts.removeLast(); } foreach (int size, sizeInts) { if (size != 0) { mainSize = size; //Set all views to same height in case user switches view break; } } if (ddSize == 0) ddSize = 1; sizeInts.clear(); sizeInts << mainSize << mainSize << mainSize << mainSize << mainSize << ddSize; viewSplitter->setSizes(sizeInts); } void MainWindow::showActiveView() { QString visibleLayout = SETTINGS.value("View/layout", "none").toString(); if (visibleLayout == "table") tableView->setHidden(false); else if (visibleLayout == "grid") gridView->setHidden(false); else if (visibleLayout == "list") listView->setHidden(false); else emptyView->setHidden(false); } void MainWindow::showMenuBar(bool mouseAtTop) { menuBar->setHidden(!mouseAtTop); } void MainWindow::showRomMenu(const QPoint &pos) { QMenu *contextMenu = new QMenu(this); QAction *contextStartAction = contextMenu->addAction(tr("&Start")); contextStartAction->setIcon(QIcon::fromTheme("media-playback-start")); connect(contextStartAction, SIGNAL(triggered()), this, SLOT(launchRomFromMenu())); if (SETTINGS.value("Other/downloadinfo", "").toString() == "true") { contextMenu->addSeparator(); QAction *contextDownloadAction = contextMenu->addAction(tr("&Download/Update Info...")); QAction *contextDeleteAction = contextMenu->addAction(tr("D&elete Current Info...")); connect(contextDownloadAction, SIGNAL(triggered()), this, SLOT(openDownloader())); connect(contextDeleteAction, SIGNAL(triggered()), this, SLOT(openDeleteDialog())); } QWidget *activeWidget = new QWidget(this); QString visibleLayout = SETTINGS.value("View/layout", "none").toString(); if (visibleLayout == "table") activeWidget = tableView->viewport(); else if (visibleLayout == "grid") activeWidget = gridView->getCurrentRomWidget(); else if (visibleLayout == "list") activeWidget = listView->getCurrentRomWidget(); contextMenu->exec(activeWidget->mapToGlobal(pos)); } void MainWindow::stopEmulator() { emulation->stopEmulator(); } void MainWindow::toggleMenus(bool active) { foreach (QAction *next, menuEnable) next->setEnabled(active); foreach (QAction *next, menuDisable) next->setEnabled(!active); tableView->setEnabled(active); gridView->setEnabled(active); listView->setEnabled(active); if (!tableView->hasSelectedRom() && !gridView->hasSelectedRom() && !listView->hasSelectedRom() && !ddView->hasSelectedRom() ) { foreach (QAction *next, menuRomSelected) next->setEnabled(false); } if (SETTINGS.value("Other/downloadinfo", "").toString() == "") { downloadAction->setEnabled(false); deleteAction->setEnabled(false); } if (SETTINGS.value("Paths/ddiplrom", "").toString() == "") ddAction->setEnabled(false); } void MainWindow::updateFullScreenMode() { if (isFullScreen()) { fullScreenAction->setChecked(false); SETTINGS.setValue("View/fullscreen", ""); menuBar->setHidden(false); tableView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); gridView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); listView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); showNormal(); } else { fullScreenAction->setChecked(true); SETTINGS.setValue("View/fullscreen", true); menuBar->setHidden(true); tableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); gridView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); showFullScreen(); } } void MainWindow::updateLayoutSetting() { QString visibleLayout = layoutGroup->checkedAction()->data().toString(); SETTINGS.setValue("View/layout", visibleLayout); sizeInts.clear(); foreach(int size, viewSplitter->sizes()) sizeInts << size; emptyView->setHidden(true); tableView->setHidden(true); gridView->setHidden(true); listView->setHidden(true); disabledView->setHidden(true); ddView->setHidden(true); int romCount = romCollection->cachedRoms(); if (romCount > 0 || visibleLayout == "none") showActiveView(); //Don't show 64DD panel for empty view QString ddipl = SETTINGS.value("Paths/ddiplrom", "").toString(); QString ddEnabled = SETTINGS.value("Emulation/64dd", "").toString(); if (visibleLayout != "none" && ddipl != "" && ddEnabled == "true") { ddView->setHidden(false); restoreSplitterSize(); } //View was updated so no ROM will be selected. Update menu items accordingly foreach (QAction *next, menuRomSelected) next->setEnabled(false); } void MainWindow::update64DD() { QString ddipl = SETTINGS.value("Paths/ddiplrom", "").toString(); if(ddAction->isChecked() && ddipl != "") { SETTINGS.setValue("Emulation/64dd", true); if (SETTINGS.value("View/layout", "none").toString() != "none") ddView->setHidden(false); viewSplitter->setSizes(QList() << 500 << 500 << 500 << 500 << 500 << 100); } else { SETTINGS.setValue("Emulation/64dd", ""); ddView->setHidden(true); } romCollection->cachedRoms(); } void MainWindow::updateStatusBar(QString message, int timeout) { statusBar->showMessage(message, timeout); } void MainWindow::updateStatusBarView() { if(statusBarAction->isChecked()) { SETTINGS.setValue("View/statusbar", true); statusBar->show(); } else { SETTINGS.setValue("View/statusbar", ""); statusBar->hide(); } } ./src/views/0000755000175000017500000000000013623376647011246 5ustar dandan./src/views/tableview.cpp0000664000175000017500000002705613623376647013750 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "tableview.h" #include "../global.h" #include "../common.h" #include "widgets/treewidgetitem.h" #include #include #include #include #include #include #include #include TableView::TableView(QWidget *parent) : QTreeWidget(parent) { this->parent = parent; setWordWrap(false); setAllColumnsShowFocus(true); setRootIsDecorated(false); setSortingEnabled(true); setContextMenuPolicy(Qt::CustomContextMenu); setStyleSheet("QTreeView { border: none; } QTreeView::item { height: 25px; }"); headerView = new QHeaderView(Qt::Horizontal, this); setHeader(headerView); setHidden(true); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), parent, SLOT(showRomMenu(const QPoint &))); connect(headerView, SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(saveSortOrder(int,Qt::SortOrder))); } void TableView::addNoCartRow() { QStringList visible = SETTINGS.value("Table/columns", "Filename|Size").toString().split("|"); fileItem = new TreeWidgetItem(this); if (visible.at(0) == "Game Cover") { fileItem->setText(6, " " + tr("No Cart")); fileItem->setForeground(6, QBrush(Qt::gray)); } else { fileItem->setText(5, " " + tr("No Cart")); fileItem->setForeground(5, QBrush(Qt::gray)); } addTopLevelItem(fileItem); } void TableView::addToTableView(Rom *currentRom) { QStringList visible = SETTINGS.value("Table/columns", "Filename|Size").toString().split("|"); if (visible.join("") == "") //Otherwise no columns, so don't bother populating return; fileItem = new TreeWidgetItem(this); //Filename for launching ROM fileItem->setText(0, currentRom->fileName); //Directory ROM is located in fileItem->setText(1, currentRom->directory); //GoodName or Internal Name for searching if (currentRom->goodName == getTranslation("Unknown ROM") || currentRom->goodName == getTranslation("Requires catalog file")) fileItem->setText(2, currentRom->internalName); else fileItem->setText(2, currentRom->goodName); //MD5 for cache info fileItem->setText(3, currentRom->romMD5.toLower()); //Zip file fileItem->setText(4, currentRom->zipFile); int i = 5, c = 0; bool addImage = false; foreach (QString current, visible) { QString text = getRomInfo(current, currentRom); fileItem->setText(i, text); if (current == "GoodName" || current == "Game Title") { if (text == getTranslation("Unknown ROM") || text == getTranslation("Requires catalog file") || text == getTranslation("Not found")) { fileItem->setForeground(i, QBrush(Qt::gray)); fileItem->setData(i, Qt::UserRole, "ZZZ"); //end of sorting } else fileItem->setData(i, Qt::UserRole, text); } if (current == "Size") fileItem->setData(i, Qt::UserRole, currentRom->sortSize); if (current == "Release Date") fileItem->setData(i, Qt::UserRole, currentRom->sortDate); if (current == "Game Cover") { c = i; addImage = true; } QStringList center, right; center << "MD5" << "CRC1" << "CRC2" << "Rumble" << "ESRB" << "Genre" << "Publisher" << "Developer"; right << "Size" << "Players" << "Save Type" << "Release Date" << "Rating"; if (center.contains(current)) fileItem->setTextAlignment(i, Qt::AlignHCenter | Qt::AlignVCenter); else if (right.contains(current)) fileItem->setTextAlignment(i, Qt::AlignRight | Qt::AlignVCenter); i++; } addTopLevelItem(fileItem); if (currentRom->imageExists && addImage) { QPixmap image(currentRom->image.scaled(getImageSize("Table"), Qt::KeepAspectRatio, Qt::SmoothTransformation)); QWidget *imageContainer = new QWidget(this); QGridLayout *imageGrid = new QGridLayout(imageContainer); QLabel *imageLabel = new QLabel(imageContainer); imageLabel->setPixmap(image); imageGrid->addWidget(imageLabel, 1, 1); imageGrid->setColumnStretch(0, 1); imageGrid->setColumnStretch(2, 1); imageGrid->setRowStretch(0, 1); imageGrid->setRowStretch(2, 1); imageGrid->setContentsMargins(0,0,0,0); imageContainer->setLayout(imageGrid); setItemWidget(fileItem, c, imageContainer); } } QString TableView::getCurrentRomInfo(QString infoName) { int index = getTableDataIndexFromName(infoName); return QVariant(currentItem()->data(index, 0)).toString(); } bool TableView::hasSelectedRom() { return currentItem() != nullptr; } void TableView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) emit enterPressed(); else if (event->key() == Qt::Key_Down && selectedItems().count() == 0) { setCurrentItem(topLevelItem(0)); emit tableActive(); } else if (event->key() == Qt::Key_Down && selectedItems().count() == 1) { int current = indexOfTopLevelItem(selectedItems().at(0)); if (current < topLevelItemCount() - 1) setCurrentItem(topLevelItem(current + 1)); } else if (event->key() == Qt::Key_Up && selectedItems().count() == 1) { int current = indexOfTopLevelItem(selectedItems().at(0)); if (current > 0) setCurrentItem(topLevelItem(current - 1)); } else QTreeWidget::keyPressEvent(event); } void TableView::resetView(bool imageUpdated) { QStringList tableVisible = SETTINGS.value("Table/columns", "Filename|Size").toString().split("|"); QStringList translations; foreach (QString header, tableVisible) translations << getTranslation(header); int hidden = 5; saveColumnWidths(); QStringList widths = SETTINGS.value("Table/width", "").toString().split("|"); headerLabels.clear(); headerLabels << "" << "" << "" << "" << "" << translations; //First 5 blank for hidden columns //Remove Game Cover title for aesthetics for (int i = 0; i < headerLabels.size(); i++) if (headerLabels.at(i) == getTranslation("Game Cover")) headerLabels.replace(i, ""); setColumnCount(headerLabels.size()); setHeaderLabels(headerLabels); headerView->setSortIndicatorShown(false); double height = 0, width = 0; if (tableVisible.contains("Game Cover")) { //Get optimal height/width for cover column height = getImageSize("Table").height() * 1.1; width = getImageSize("Table").width() * 1.2; setStyleSheet("QTreeView { border: none; } QTreeView::item { height: " + QString::number(static_cast(std::round(height))) + "px; }"); } else setStyleSheet("QTreeView { border: none; } QTreeView::item { height: 25px; }"); QStringList sort = SETTINGS.value("Table/sort", "").toString().split("|"); if (sort.size() == 2) { if (sort[1] == "descending") headerView->setSortIndicator(tableVisible.indexOf(sort[0]) + hidden, Qt::DescendingOrder); else headerView->setSortIndicator(tableVisible.indexOf(sort[0]) + hidden, Qt::AscendingOrder); } setColumnHidden(0, true); //Hidden filename for launching emulator setColumnHidden(1, true); //Hidden directory of ROM location setColumnHidden(2, true); //Hidden goodname for searching setColumnHidden(3, true); //Hidden md5 for cache info setColumnHidden(4, true); //Hidden column for zip file int i = hidden; foreach (QString current, tableVisible) { if (i == hidden) { int c = i; if (current == "Game Cover") c++; //If first column is game cover, use next column if (SETTINGS.value("Table/stretchfirstcolumn", "true") == "true") header()->setSectionResizeMode(c, QHeaderView::Stretch); else header()->setSectionResizeMode(c, QHeaderView::Interactive); } if (widths.size() == tableVisible.size()) setColumnWidth(i, widths[i - hidden].toInt()); else setColumnWidth(i, getDefaultWidth(current, static_cast(std::round(width)))); //Overwrite saved value if switching image sizes if (imageUpdated && current == "Game Cover") setColumnWidth(i, static_cast(std::round(width))); i++; } } void TableView::saveColumnWidths() { QStringList widths; for (int i = 5; i < columnCount(); i++) { widths << QString::number(columnWidth(i)); } if (widths.size() > 0) SETTINGS.setValue("Table/width", widths.join("|")); } void TableView::saveSortOrder(int column, Qt::SortOrder order) { QString columnName = headerLabels.value(column); if (order == Qt::DescendingOrder) SETTINGS.setValue("Table/sort", columnName + "|descending"); else SETTINGS.setValue("Table/sort", columnName + "|ascending"); } void TableView::saveTablePosition() { positionx = horizontalScrollBar()->value(); positiony = verticalScrollBar()->value(); if (selectedItems().count() > 0) { int index = getTableDataIndexFromName("fileName"); savedTableRom = indexOfTopLevelItem(selectedItems().at(0)); savedTableRomFilename = QVariant(topLevelItem(savedTableRom)->data(index, 0)).toString(); } else { savedTableRom = -1; savedTableRomFilename = ""; } } void TableView::setTablePosition() { horizontalScrollBar()->setValue(positionx); verticalScrollBar()->setValue(positiony); //Restore selected ROM if it is in the same position if (savedTableRom >= 0) { int index = getTableDataIndexFromName("fileName"); QString checkFilename = QVariant(topLevelItem(savedTableRom)->data(index, 0)).toString(); if (savedTableRomFilename == checkFilename) { setCurrentItem(topLevelItem(savedTableRom)); emit tableActive(); } } } ./src/views/listview.h0000664000175000017500000000507313623376647013274 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef LISTVIEW_H #define LISTVIEW_H #include class QVBoxLayout; struct Rom; class ListView : public QScrollArea { Q_OBJECT public: explicit ListView(QWidget *parent = 0); void addToListView(Rom *currentRom, int count, bool ddEnabled); int getCurrentRom(); QString getCurrentRomInfo(QString infoName); QWidget *getCurrentRomWidget(); bool hasSelectedRom(); void resetView(); void saveListPosition(); void setListBackground(); protected: void keyPressEvent(QKeyEvent *event); signals: void listItemSelected(bool active); private: int currentListRom; bool listCurrent; int savedListRom; QString savedListRomFilename; int positionx; int positiony; QVBoxLayout *listLayout; QWidget *listWidget; QWidget *parent; private slots: void highlightListWidget(QWidget *current); void highlightListWidgetSetMargin(); void selectNextRom(QWidget *current, QString keypress); void setListPosition(); }; #endif // LISTVIEW_H ./src/views/gridview.h0000664000175000017500000000517513623376647013251 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef GRIDVIEW_H #define GRIDVIEW_H #include class QGridLayout; struct Rom; class GridView : public QScrollArea { Q_OBJECT public: explicit GridView(QWidget *parent = 0); void addToGridView(Rom *currentRom, int count, bool ddEnabled); int getCurrentRom(); QString getCurrentRomInfo(QString infoName); QWidget *getCurrentRomWidget(); bool hasSelectedRom(); void resetView(); void saveGridPosition(); void setGridBackground(); protected: void keyPressEvent(QKeyEvent *event); void resizeEvent(QResizeEvent *event); signals: void gridItemSelected(bool active); private: void updateGridColumns(int width); int autoColumnCount; int currentGridRom; bool gridCurrent; int savedGridRom; QString savedGridRomFilename; int positionx; int positiony; QGridLayout *gridLayout; QWidget *gridWidget; QWidget *parent; private slots: void highlightGridWidget(QWidget *current); void selectNextRom(QWidget *current, QString keypress); void setGridPosition(); }; #endif // GRIDVIEW_H ./src/views/tableview.h0000664000175000017500000000467213623376647013414 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef TABLEVIEW_H #define TABLEVIEW_H #include class TreeWidgetItem; struct Rom; class TableView : public QTreeWidget { Q_OBJECT public: explicit TableView(QWidget *parent = 0); void addNoCartRow(); void addToTableView(Rom *currentRom); QString getCurrentRomInfo(QString infoName); bool hasSelectedRom(); void resetView(bool imageUpdated); void saveColumnWidths(); void saveTablePosition(); protected: void keyPressEvent(QKeyEvent *event); signals: void enterPressed(); void tableActive(); private: int positionx; int positiony; int savedTableRom; QString savedTableRomFilename; QStringList headerLabels; QHeaderView *headerView; QWidget *parent; TreeWidgetItem *fileItem; private slots: void saveSortOrder(int column, Qt::SortOrder order); void setTablePosition(); }; #endif // TABLEVIEW_H ./src/views/ddview.h0000664000175000017500000000374613623376647012715 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef DDVIEW_H #define DDVIEW_H #include class TreeWidgetItem; struct Rom; class DDView : public QTreeWidget { Q_OBJECT public: explicit DDView(QWidget *parent = 0); void addNoDiskRow(); QString getCurrentRomInfo(QString infoName); bool hasSelectedRom(); private: QWidget *parent; TreeWidgetItem *fileItem; private slots: void addTo64DDView(Rom *currentRom); }; #endif // DDVIEW_H ./src/views/listview.cpp0000664000175000017500000002536313623376647013633 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "listview.h" #include "../global.h" #include "../common.h" #include "widgets/clickablewidget.h" #include #include #include #include #include #include #include #include ListView::ListView(QWidget *parent) : QScrollArea(parent) { this->parent = parent; setObjectName("listView"); setWidgetResizable(true); setHidden(true); setListBackground(); listWidget = new QWidget(this); listWidget->setObjectName("listWidget"); setWidget(listWidget); listLayout = new QVBoxLayout(listWidget); listLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); listWidget->setLayout(listLayout); listCurrent = false; currentListRom = 0; } void ListView::addToListView(Rom *currentRom, int count, bool ddEnabled) { if (ddEnabled) // Add place for "No Cart" entry count++; QStringList visible = SETTINGS.value("List/columns", "Filename|Internal Name|Size").toString().split("|"); if (visible.join("") == "" && SETTINGS.value("List/displaycover", "") != "true") //Otherwise no columns, so don't bother populating return; ClickableWidget *gameListItem = new ClickableWidget(listWidget); gameListItem->setContentsMargins(0, 0, 20, 0); gameListItem->setContextMenuPolicy(Qt::CustomContextMenu); if (SETTINGS.value("List/theme","Light").toString() == "Dark") gameListItem->setStyleSheet("color:#EEE;"); //Assign ROM data to widget for use in click events gameListItem->setProperty("fileName", currentRom->fileName); gameListItem->setProperty("directory", currentRom->directory); if (currentRom->goodName == getTranslation("Unknown ROM") || currentRom->goodName == getTranslation("Requires catalog file")) gameListItem->setProperty("search", currentRom->internalName); else gameListItem->setProperty("search", currentRom->goodName); gameListItem->setProperty("romMD5", currentRom->romMD5); gameListItem->setProperty("zipFile", currentRom->zipFile); QGridLayout *gameListLayout = new QGridLayout(gameListItem); gameListLayout->setColumnStretch(3, 1); //Add image if (SETTINGS.value("List/displaycover", "") == "true") { QLabel *listImageLabel = new QLabel(gameListItem); listImageLabel->setMinimumHeight(getImageSize("List").height()); listImageLabel->setMinimumWidth(getImageSize("List").width()); QPixmap image; if (currentRom->imageExists) image = currentRom->image.scaled(getImageSize("List"), Qt::KeepAspectRatio, Qt::SmoothTransformation); else { if (ddEnabled && count == 0) image = QPixmap(":/images/no-cart.png").scaled(getImageSize("List"), Qt::KeepAspectRatio, Qt::SmoothTransformation); else image = QPixmap(":/images/not-found.png").scaled(getImageSize("List"), Qt::KeepAspectRatio, Qt::SmoothTransformation); } listImageLabel->setPixmap(image); listImageLabel->setAlignment(Qt::AlignCenter); gameListLayout->addWidget(listImageLabel, 0, 1); } //Create text label QLabel *listTextLabel = new QLabel("", gameListItem); QString listText = ""; int i = 0; foreach (QString current, visible) { QString addition = ""; if (i == 0 && SETTINGS.value("List/firstitemheader","true") == "true") addition += "

"; else addition += "
" + getTranslation(current) + ": "; addition += getRomInfo(current, currentRom, true); if (i == 0 && SETTINGS.value("List/firstitemheader","true") == "true") addition += "

"; else addition += ""; if (addition.right(12) != ": ") listText += addition; i++; } if (ddEnabled && count == 0) listText = "

" + tr("No Cart") + "

"; listTextLabel->setText(listText); listTextLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); listTextLabel->setWordWrap(true); QFont font = listTextLabel->font(); font.setPointSize(getTextSize()); listTextLabel->setFont(font); gameListLayout->addWidget(listTextLabel, 0, 3); gameListLayout->setColumnMinimumWidth(0, 20); gameListLayout->setColumnMinimumWidth(2, 10); gameListItem->setLayout(gameListLayout); if (count != 0) { QFrame *separator = new QFrame(); separator->setFrameShape(QFrame::HLine); separator->setStyleSheet("margin:0;padding:0;"); QPalette palette = separator->palette(); if (SETTINGS.value("List/theme","Light").toString() == "Dark") palette.setColor(QPalette::Window, Qt::black); else palette.setColor(QPalette::Window, Qt::gray); separator->setPalette(palette); listLayout->addWidget(separator); } listLayout->addWidget(gameListItem); connect(gameListItem, SIGNAL(singleClicked(QWidget*)), this, SLOT(highlightListWidget(QWidget*))); connect(gameListItem, SIGNAL(doubleClicked(QWidget*)), parent, SLOT(launchRomFromWidget(QWidget*))); connect(gameListItem, SIGNAL(arrowPressed(QWidget*, QString)), this, SLOT(selectNextRom(QWidget*, QString))); connect(gameListItem, SIGNAL(enterPressed(QWidget*)), parent, SLOT(launchRomFromWidget(QWidget*))); connect(gameListItem, SIGNAL(customContextMenuRequested(const QPoint &)), parent, SLOT(showRomMenu(const QPoint &))); } int ListView::getCurrentRom() { return currentListRom; } QString ListView::getCurrentRomInfo(QString infoName) { const char *property = infoName.toUtf8().constData(); if (listLayout->count() > currentListRom) return listLayout->itemAt(currentListRom)->widget()->property(property).toString(); return ""; } QWidget *ListView::getCurrentRomWidget() { return listLayout->itemAt(currentListRom)->widget(); } bool ListView::hasSelectedRom() { return listCurrent; } void ListView::highlightListWidget(QWidget *current) { current->setFocus(); QLayoutItem *listItem; for (int item = 0; (listItem = listLayout->itemAt(item)) != nullptr; item++) { if (listItem->widget() == current) currentListRom = item; } //Give current left margin to stand out //Delay with QTimer so right-click menu appears correctly QTimer::singleShot(5, this, SLOT(highlightListWidgetSetMargin())); listCurrent = true; emit listItemSelected(true); } void ListView::highlightListWidgetSetMargin() { //Reset all margins QLayoutItem *listItem; for (int item = 0; (listItem = listLayout->itemAt(item)) != nullptr; item++) listItem->widget()->setContentsMargins(0, 0, 20, 0); getCurrentRomWidget()->setContentsMargins(20, 0, 0, 0); } void ListView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Down && listLayout->count() > 0) { highlightListWidget(listLayout->itemAt(0)->widget()); ensureWidgetVisible(listLayout->itemAt(0)->widget()); } else QScrollArea::keyPressEvent(event); } void ListView::resetView() { QLayoutItem *listItem; while ((listItem = listLayout->takeAt(0)) != nullptr) { delete listItem->widget(); delete listItem; } listCurrent = false; } void ListView::saveListPosition() { positionx = horizontalScrollBar()->value(); positiony = verticalScrollBar()->value(); if (listCurrent) savedListRom = currentListRom; else savedListRom = -1; savedListRomFilename = getCurrentRomInfo("fileName"); } void ListView::selectNextRom(QWidget* current, QString keypress) { int offset = 0; if (keypress == "UP" || keypress == "LEFT") offset = -2; else if (keypress == "DOWN" || keypress == "RIGHT") offset = 2; QLayoutItem *listItem; for (int item = 0; (listItem = listLayout->itemAt(item)) != nullptr; item++) { if (listItem->widget() == current && item + offset >= 0 && listLayout->itemAt(item + offset) != nullptr) { ensureWidgetVisible(listLayout->itemAt(item + offset)->widget()); highlightListWidget(listLayout->itemAt(item + offset)->widget()); } } } void ListView::setListBackground() { if (SETTINGS.value("List/theme","Light").toString() == "Dark") setStyleSheet("#listView { border: none; background: #222; } #listWidget { background: transparent; }"); else setStyleSheet("#listView { border: none; background: #FFF; } #listWidget { background: transparent; }"); } void ListView::setListPosition() { horizontalScrollBar()->setValue(positionx); verticalScrollBar()->setValue(positiony); //Restore selected ROM if it is in the same position if (savedListRom != -1 && listLayout->count() > savedListRom) { QWidget *checkWidget = listLayout->itemAt(savedListRom)->widget(); if (checkWidget->property("fileName").toString() == savedListRomFilename) highlightListWidget(checkWidget); } } ./src/views/widgets/0000755000175000017500000000000013623376647012714 5ustar dandan./src/views/widgets/clickablewidget.cpp0000664000175000017500000000507613623376647016547 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "clickablewidget.h" #include ClickableWidget::ClickableWidget(QWidget *parent) : QWidget(parent) { setFocusPolicy(Qt::StrongFocus); } void ClickableWidget::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Up) emit arrowPressed(this, "UP"); else if (event->key() == Qt::Key_Down) emit arrowPressed(this, "DOWN"); else if (event->key() == Qt::Key_Left) emit arrowPressed(this, "LEFT"); else if (event->key() == Qt::Key_Right) emit arrowPressed(this, "RIGHT"); else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) emit enterPressed(this); else QWidget::keyPressEvent(event); } void ClickableWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) emit singleClicked(this); } void ClickableWidget::mouseDoubleClickEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) emit doubleClicked(this); } ./src/views/widgets/treewidgetitem.h0000664000175000017500000000352613623376647016117 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef TREEWIDGETITEM_H #define TREEWIDGETITEM_H #include class TreeWidgetItem : public QTreeWidgetItem { public: TreeWidgetItem(QTreeWidget *parent) : QTreeWidgetItem(parent) {} bool operator< (const QTreeWidgetItem &other) const; }; #endif // TREEWIDGETITEM_H ./src/views/widgets/clickablewidget.h0000664000175000017500000000417113623376647016207 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef CLICKABLEWIDGET_H #define CLICKABLEWIDGET_H #include class QMouseEvent; class ClickableWidget : public QWidget { Q_OBJECT public: explicit ClickableWidget(QWidget *parent = 0); protected: void keyPressEvent(QKeyEvent* event); void mousePressEvent(QMouseEvent *event); void mouseDoubleClickEvent(QMouseEvent *event); signals: void arrowPressed(QWidget *current, QString keypress); void enterPressed(QWidget *current); void singleClicked(QWidget *current); void doubleClicked(QWidget *current); }; #endif // CLICKABLEWIDGET_H ./src/views/widgets/treewidgetitem.cpp0000664000175000017500000000470213623376647016447 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "treewidgetitem.h" //Reimplemented QTreeWidgetItem operator to sort based on both integers and text bool TreeWidgetItem::operator< (const QTreeWidgetItem &other) const { int column = treeWidget()->sortColumn(); if (data(column, Qt::UserRole).toString() != "") { if (data(column, Qt::UserRole).toString().contains(QRegExp("[^\\d]+"))) { QString firstText = data(column, Qt::UserRole).toString(); QString otherText = other.data(column, Qt::UserRole).toString(); return firstText < otherText; } else { int firstNumber = data(column, Qt::UserRole).toInt(); int otherNumber = other.data(column, Qt::UserRole).toInt(); return firstNumber < otherNumber; } } else { QString firstText = text(column); QString otherText = other.text(column); return firstText < otherText; } } ./src/views/ddview.cpp0000664000175000017500000000664113623376647013245 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "ddview.h" #include "../common.h" #include "../global.h" #include "widgets/treewidgetitem.h" #include #include DDView::DDView(QWidget *parent) : QTreeWidget(parent) { this->parent = parent; setWordWrap(false); setAllColumnsShowFocus(true); setRootIsDecorated(false); setStyleSheet("QTreeView { border: none; } QTreeView::item { height: 25px; }"); setHeaderLabels(QStringList() << "" << "" << "" << "" << "" << "64DD ROM"); header()->setHidden(true); setColumnHidden(0, true); //Hidden filename for launching emulator setColumnHidden(1, true); //Hidden directory of ROM location setColumnHidden(2, true); //Hidden goodname for searching setColumnHidden(3, true); //Hidden md5 for cache info setColumnHidden(4, true); //Hidden column for zip file setHidden(true); } void DDView::addNoDiskRow() { fileItem = new TreeWidgetItem(this); fileItem->setText(5, tr("No Disk")); fileItem->setForeground(5, QBrush(Qt::gray)); addTopLevelItem(fileItem); } void DDView::addTo64DDView(Rom *currentRom) { fileItem = new TreeWidgetItem(this); fileItem->setText(0, currentRom->fileName); //Filename for launching ROM fileItem->setText(1, currentRom->directory); //Directory ROM is located in fileItem->setText(2, ""); //GoodName or Internal Name for searching (currently blank) fileItem->setText(3, currentRom->romMD5.toLower()); //MD5 for cache info fileItem->setText(4, currentRom->zipFile); //Zip file fileItem->setText(5, QFileInfo(currentRom->fileName).completeBaseName()); //Visible filename addTopLevelItem(fileItem); } QString DDView::getCurrentRomInfo(QString infoName) { int index = getTableDataIndexFromName(infoName); return QVariant(currentItem()->data(index, 0)).toString(); } bool DDView::hasSelectedRom() { return currentItem() != nullptr; } ./src/views/gridview.cpp0000664000175000017500000003075113623376647013602 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "gridview.h" #include "../global.h" #include "../common.h" #include "widgets/clickablewidget.h" #include #include #include #include #include #include #include GridView::GridView(QWidget *parent) : QScrollArea(parent) { this->parent = parent; setObjectName("gridView"); setStyleSheet("#gridView { border: none; }"); setBackgroundRole(QPalette::Dark); setAlignment(Qt::AlignHCenter); setHidden(true); setGridBackground(); gridWidget = new QWidget(this); gridWidget->setObjectName("gridWidget"); gridWidget->setStyleSheet("#gridWidget { background: transparent; }"); setWidget(gridWidget); gridLayout = new QGridLayout(gridWidget); gridLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); gridLayout->setRowMinimumHeight(0, 10); gridWidget->setLayout(gridLayout); gridCurrent = false; currentGridRom = 0; } void GridView::addToGridView(Rom *currentRom, int count, bool ddEnabled) { if (ddEnabled) // Add place for "No Cart" entry count++; ClickableWidget *gameGridItem = new ClickableWidget(gridWidget); gameGridItem->setMinimumWidth(getGridSize("width")); gameGridItem->setMaximumWidth(getGridSize("width")); gameGridItem->setGraphicsEffect(getShadow(false)); gameGridItem->setContextMenuPolicy(Qt::CustomContextMenu); //Assign ROM data to widget for use in click events gameGridItem->setProperty("fileName", currentRom->fileName); gameGridItem->setProperty("directory", currentRom->directory); if (currentRom->goodName == getTranslation("Unknown ROM") || currentRom->goodName == getTranslation("Requires catalog file")) gameGridItem->setProperty("search", currentRom->internalName); else gameGridItem->setProperty("search", currentRom->goodName); gameGridItem->setProperty("romMD5", currentRom->romMD5); gameGridItem->setProperty("zipFile", currentRom->zipFile); QGridLayout *gameGridLayout = new QGridLayout(gameGridItem); gameGridLayout->setColumnStretch(0, 1); gameGridLayout->setColumnStretch(3, 1); gameGridLayout->setRowMinimumHeight(1, getImageSize("Grid").height()); QLabel *gridImageLabel = new QLabel(gameGridItem); gridImageLabel->setMinimumHeight(getImageSize("Grid").height()); gridImageLabel->setMinimumWidth(getImageSize("Grid").width()); QPixmap image; if (currentRom->imageExists) { //Use uniform aspect ratio to account for fluctuations in TheGamesDB box art Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio; //Don't warp aspect ratio though if image is too far away from standard size (JP box art) double aspectRatio = double(currentRom->image.width()) / currentRom->image.height(); if (aspectRatio < 1.1 || aspectRatio > 1.8) aspectRatioMode = Qt::KeepAspectRatio; image = currentRom->image.scaled(getImageSize("Grid"), aspectRatioMode, Qt::SmoothTransformation); } else { if (ddEnabled && count == 0) image = QPixmap(":/images/no-cart.png").scaled(getImageSize("Grid"), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); else image = QPixmap(":/images/not-found.png").scaled(getImageSize("Grid"), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } gridImageLabel->setPixmap(image); gridImageLabel->setAlignment(Qt::AlignCenter); gameGridLayout->addWidget(gridImageLabel, 1, 1); if (SETTINGS.value("Grid/label","true") == "true") { QLabel *gridTextLabel = new QLabel(gameGridItem); //Don't allow label to be wider than image gridTextLabel->setMaximumWidth(getImageSize("Grid").width()); QString text = ""; QString labelText = SETTINGS.value("Grid/labeltext","Filename").toString(); text = getRomInfo(labelText, currentRom); if (ddEnabled && count == 0) text = tr("No Cart"); gridTextLabel->setText(text); QString textHex = getColor(SETTINGS.value("Grid/labelcolor","White").toString()).name(); int fontSize = getGridSize("font"); gridTextLabel->setStyleSheet("QLabel { font-weight: bold; color: " + textHex + "; font-size: " + QString::number(fontSize) + "px; }"); gridTextLabel->setWordWrap(true); gridTextLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop); gameGridLayout->addWidget(gridTextLabel, 2, 1); } gameGridItem->setLayout(gameGridLayout); gameGridItem->setMinimumHeight(gameGridItem->sizeHint().height()); int columnCount; if (SETTINGS.value("Grid/autocolumns","true").toString() == "true") columnCount = viewport()->width() / (getGridSize("width") + 10); else columnCount = SETTINGS.value("Grid/columncount", "4").toInt(); if (columnCount == 0) columnCount = 1; gridLayout->addWidget(gameGridItem, count / columnCount + 1, count % columnCount + 1); gridWidget->adjustSize(); connect(gameGridItem, SIGNAL(singleClicked(QWidget*)), this, SLOT(highlightGridWidget(QWidget*))); connect(gameGridItem, SIGNAL(doubleClicked(QWidget*)), parent, SLOT(launchRomFromWidget(QWidget*))); connect(gameGridItem, SIGNAL(arrowPressed(QWidget*, QString)), this, SLOT(selectNextRom(QWidget*, QString))); connect(gameGridItem, SIGNAL(enterPressed(QWidget*)), parent, SLOT(launchRomFromWidget(QWidget*))); connect(gameGridItem, SIGNAL(customContextMenuRequested(const QPoint &)), parent, SLOT(showRomMenu(const QPoint &))); } int GridView::getCurrentRom() { return currentGridRom; } QString GridView::getCurrentRomInfo(QString infoName) { const char *property = infoName.toUtf8().constData(); if (gridLayout->count() > currentGridRom) return gridLayout->itemAt(currentGridRom)->widget()->property(property).toString(); return ""; } QWidget *GridView::getCurrentRomWidget() { return gridLayout->itemAt(currentGridRom)->widget(); } bool GridView::hasSelectedRom() { return gridCurrent; } void GridView::highlightGridWidget(QWidget *current) { current->setFocus(); //Set all to inactive shadow QLayoutItem *gridItem; for (int item = 0; (gridItem = gridLayout->itemAt(item)) != nullptr; item++) { gridItem->widget()->setGraphicsEffect(getShadow(false)); if (gridItem->widget() == current) currentGridRom = item; } //Set current to active shadow current->setGraphicsEffect(getShadow(true)); gridCurrent = true; emit gridItemSelected(true); } void GridView::keyPressEvent(QKeyEvent *event) { if ((event->key() == Qt::Key_Down || event->key() == Qt::Key_Right) && gridLayout->count() > 0) { highlightGridWidget(gridLayout->itemAt(0)->widget()); ensureWidgetVisible(gridLayout->itemAt(0)->widget()); } else QScrollArea::keyPressEvent(event); } void GridView::resetView() { QLayoutItem *gridItem; while ((gridItem = gridLayout->takeAt(0)) != nullptr) { delete gridItem->widget(); delete gridItem; } gridCurrent = false; } void GridView::resizeEvent(QResizeEvent *event) { int check = event->size().width() / (getGridSize("width") + 10); bool autoAdjustColumns = SETTINGS.value("Grid/autocolumns","true").toString() == "true"; if (autoAdjustColumns && check != autoColumnCount && check != 0) { autoColumnCount = check; updateGridColumns(event->size().width()); } else QScrollArea::resizeEvent(event); } void GridView::saveGridPosition() { positionx = horizontalScrollBar()->value(); positiony = verticalScrollBar()->value(); if (gridCurrent) savedGridRom = currentGridRom; else savedGridRom = -1; savedGridRomFilename = getCurrentRomInfo("fileName"); } void GridView::selectNextRom(QWidget* current, QString keypress) { int columnCount; if (SETTINGS.value("Grid/autocolumns","true").toString() == "true") columnCount = autoColumnCount; else columnCount = SETTINGS.value("Grid/columncount", "4").toInt(); int offset = 0; if (keypress == "UP") offset = columnCount * -1; else if (keypress == "DOWN") offset = columnCount; else if (keypress == "RIGHT") offset = 1; else if (keypress == "LEFT") offset = -1; QLayoutItem *gridItem; for (int item = 0; (gridItem = gridLayout->itemAt(item)) != nullptr; item++) { if (gridItem->widget() == current && item + offset >= 0 && gridLayout->itemAt(item + offset) != nullptr) { ensureWidgetVisible(gridLayout->itemAt(item + offset)->widget()); highlightGridWidget(gridLayout->itemAt(item + offset)->widget()); } } } void GridView::setGridBackground() { QString theme = SETTINGS.value("Grid/theme", "Normal").toString(); if (theme == "Light") setStyleSheet("#gridView { border: none; background: #FFF; } #gridWidget { background: transparent; }"); else if (theme == "Dark") setStyleSheet("#gridView { border: none; background: #222; } #gridWidget { background: transparent; }"); else setStyleSheet("#gridView { border: none; }"); QString background = SETTINGS.value("Grid/background", "").toString(); if (background != "") { QFile backgroundFile(background); if (backgroundFile.exists() && !QFileInfo(backgroundFile).isDir()) setStyleSheet(QString() + "#gridView { " + "border: none; " + "background: url(" + background + "); " + "background-attachment: fixed; " + "background-position: top center; " + "} #gridWidget { background: transparent; } " ); } } void GridView::setGridPosition() { horizontalScrollBar()->setValue(positionx); verticalScrollBar()->setValue(positiony); //Restore selected ROM if it is in the same position if (savedGridRom != -1 && gridLayout->count() > savedGridRom) { QWidget *checkWidget = gridLayout->itemAt(savedGridRom)->widget(); if (checkWidget->property("fileName").toString() == savedGridRomFilename) highlightGridWidget(checkWidget); } if (SETTINGS.value("Grid/autocolumns","true").toString() == "true") setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); else setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); } void GridView::updateGridColumns(int width) { int columnCount = width / (getGridSize("width") + 10); int gridCount = gridLayout->count(); QList gridItems; for (int count = 0; count < gridCount; count++) gridItems << gridLayout->takeAt(0)->widget(); int count = 0; foreach(QWidget *gridItem, gridItems) { gridLayout->addWidget(gridItem, count / columnCount + 1, count % columnCount + 1); count++; } gridWidget->adjustSize(); } ./src/dialogs/0000755000175000017500000000000013623376647011533 5ustar dandan./src/dialogs/settingsdialog.ui0000664000175000017500000016317213623376647015126 0ustar dandan SettingsDialog 0 0 600 675 550 650 Settings QTabWidget::Rounded 0 Paths true Save Files Browse... 16 kbit EEPROM file: FlashRAM file: Browse... Browse... Save directory: Browse... 4 kbit EEPROM file: Browse... SRAM file: Specify individual files for saves CEN64 Files Browse... Browse... Browse... PIF IPL file: ROM catalog: CEN64 executable: Browse... 64DD IPL file: Qt::Vertical 20 40 16777215 120 ROM Directories 16777215 80 CEN64-Qt will search for all .z64, .n64, .ndd, and .zip files in these directories 16777215 80 Add... Remove Emulation Qt::Horizontal 40 20 10 10 Run multithreaded (quasi-accurate): No audio: No video: Qt::Vertical 20 40 Controllers 0 Controller #1 Qt::Vertical 20 40 Transfer Pak ROM: Transfer Pak Save: Controller Pak File: Accessory: Enabled: Browse... Browse... Browse... Controller #2 Transfer Pak Save: Transfer Pak ROM: Controller Pak File: Accessory: Enabled: Browse... Browse... Browse... Qt::Vertical 20 40 Controller #3 Transfer Pak ROM: Transfer Pak Save: Controller Pak File: Accessory: Enabled: Browse... Browse... Browse... Qt::Vertical 20 40 Controller #4 Transfer Pak ROM: Transfer Pak Save: Controller Pak File: Accessory: Enabled: Browse... Browse... Browse... Qt::Vertical 20 40 Table Qt::Horizontal 40 20 Qt::Vertical 20 40 Qt::Horizontal 40 20 20 0 Image: 160 250 160 300 16777215 80 ... Qt::UpArrow ... Qt::DownArrow 160 250 160 300 16777215 80 ... Qt::RightArrow ... Qt::LeftArrow Current Columns: Available Columns: Stretch First Column Grid ROM Label QLayout::SetMinAndMaxSize Show Label: 150 0 150 16777215 Label Text: Label Color: 100 16777215 Qt::Horizontal 40 20 Qt::Vertical 20 40 Other Browse... Custom Background: Sorting: Descending 150 16777215 Background: ROM Image Qt::Horizontal 40 20 0 0 0 0 Shadow Inactive Color: Shadow Active Color: Columns: Image Size: 100 16777215 100 16777215 1 20 100 16777215 false 100 16777215 Adjust Automatically List Qt::Horizontal 40 20 20 0 Current Items: 16777215 80 ... Qt::RightArrow ... Qt::LeftArrow 160 250 16777215 80 ... Qt::UpArrow ... Qt::DownArrow 160 250 Available Items: Image Size: Descending Sorting: 130 16777215 First Item as Header Display Cover Image Text Size: Theme: Qt::Horizontal 40 20 Qt::Vertical 20 40 Other 10 10 150 0 Additional Parameters: Requires catalog file 330 0 Download Game Information (thegamesdb.net): Output to Console: Use to pass additional parameters to CEN64 Language: Qt::Vertical 20 40 Qt::Horizontal 40 20 <b>Note:</b> Language changes will not take place until application restart. true 10 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok tabWidget emulatorPath pifPath ddPath catalogPath emulatorButton pifButton ddButton catalogButton romList romAddButton romRemoveButton savesPath savesButton saveOption eeprom4kPath eeprom16kPath sramPath flashPath eeprom4kButton eeprom16kButton sramButton flashButton multithreadOption noAudioOption noVideoOption controllersTabWidget ctrl1Enabled ctrl1Accessory ctrl1MemPak ctrl1TPakROM ctrl1TPakSave ctrl2Enabled ctrl2Accessory ctrl2MemPak ctrl2TPakROM ctrl2TPakSave ctrl3Enabled ctrl3Accessory ctrl3MemPak ctrl3TPakROM ctrl3TPakSave ctrl4Enabled ctrl4Accessory ctrl4MemPak ctrl4TPakROM ctrl4TPakSave tableAvailableList tableAddButton tableRemoveButton tableCurrentList tableSortDownButton tableSortUpButton tableSizeBox tableStretchOption gridSizeBox columnCountBox autoColumnOption shadowInactiveBox shadowActiveBox labelOption labelTextBox labelColorBox bgThemeBox backgroundPath backgroundButton gridSortBox gridDescendingOption listAvailableList listAddButton listRemoveButton listCurrentList listSortDownButton listSortUpButton listCoverOption listHeaderOption listSizeBox listTextBox listThemeBox listSortBox listDescendingOption downloadOption outputOption parametersLine languageBox ./src/dialogs/downloaddialog.cpp0000664000175000017500000000705013623376647015232 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "downloaddialog.h" #include "../common.h" #include "../roms/thegamesdbscraper.h" #include #include #include #include DownloadDialog::DownloadDialog(QString fileText, QString defaultText, QString romMD5, QWidget *parent) : QDialog(parent) { this->romMD5 = romMD5; this->parent = parent; setWindowTitle(tr("Search Game Information")); downloadLayout = new QGridLayout(this); fileLabel = new QLabel("" + tr("File") + ": " + fileText, this); gameNameLabel = new QLabel(tr("Name of Game:"), this); gameIDLabel = new QLabel(tr("or Game ID:"), this); defaultText.remove(QRegExp("\\W*(\\(|\\[).+(\\)|\\])\\W*")); gameNameField = new QLineEdit(defaultText, this); gameIDField = new QLineEdit(this); gameIDField->setToolTip(tr("From thegamesdb.net URL of game")); downloadButtonBox = new QDialogButtonBox(Qt::Horizontal, this); downloadButtonBox->addButton(tr("Search"), QDialogButtonBox::AcceptRole); downloadButtonBox->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); downloadLayout->addWidget(fileLabel, 0, 0, 1, 2); downloadLayout->addWidget(gameNameLabel, 1, 0); downloadLayout->addWidget(gameIDLabel, 2, 0); downloadLayout->addWidget(gameNameField, 1, 1); downloadLayout->addWidget(gameIDField, 2, 1); downloadLayout->addWidget(downloadButtonBox, 4, 0, 1, 3); downloadLayout->setRowStretch(3,1); downloadLayout->setColumnStretch(1,1); downloadLayout->setColumnMinimumWidth(1, 300); downloadLayout->setRowMinimumHeight(0, 20); downloadLayout->setRowMinimumHeight(3, 20); connect(downloadButtonBox, SIGNAL(accepted()), this, SLOT(runDownloader())); connect(downloadButtonBox, SIGNAL(rejected()), this, SLOT(close())); setLayout(downloadLayout); } void DownloadDialog::runDownloader() { close(); scraper = new TheGamesDBScraper(parent, true); scraper->downloadGameInfo(romMD5, gameNameField->text(), gameIDField->text()); } ./src/dialogs/downloaddialog.h0000664000175000017500000000435613623376647014705 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef DOWNLOADDIALOG_H #define DOWNLOADDIALOG_H #include class QDialogButtonBox; class QGridLayout; class QLabel; class QLineEdit; class TheGamesDBScraper; class DownloadDialog : public QDialog { Q_OBJECT public: explicit DownloadDialog(QString fileText, QString defaultText, QString romMD5, QWidget *parent = 0); private: QDialogButtonBox *downloadButtonBox; QGridLayout *downloadLayout; QLabel *fileLabel; QLabel *gameNameLabel; QLabel *gameIDLabel; QLineEdit *gameNameField; QLineEdit *gameIDField; QString romMD5; QWidget *parent; TheGamesDBScraper *scraper; private slots: void runDownloader(); }; #endif // DOWNLOADDIALOG_H ./src/dialogs/v64converter.cpp0000664000175000017500000001142613623376647014614 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "v64converter.h" #include "../global.h" #include #include V64Converter::V64Converter(QString romPath, QWidget *parent) : QObject(parent) { QDir romDir(romPath); QString v64File = QFileDialog::getOpenFileName(parent, tr("Open v64/n64 File"), romPath, tr("V64/n64 ROMs") + " (*.v64 *.n64);;" + tr("All Files") + " (*)"); if (v64File != "") { QString defaultFileName = QFileInfo(v64File).completeBaseName() + ".z64"; QString defaultFile = romDir.absoluteFilePath(defaultFileName); QString saveFile = QFileDialog::getSaveFileName(parent, tr("Save z64 File"), defaultFile, tr("Z64 ROMs") + " (*.z64);;" + tr("All Files") + " (*)"); if (saveFile != "") runConverter(v64File, saveFile, parent); } } void V64Converter::runConverter(QString v64File, QString saveFile, QWidget *parent) { QFile v64(v64File); v64.open(QIODevice::ReadOnly); QString v64Check(v64.read(4).toHex()), message; if (v64Check == "80371240") { message = "\"" + QFileInfo(v64).fileName() + "\" " + tr("already in z64 format!"); QMessageBox::warning(parent, tr(" Converter").replace("",AppName), message); } else if (v64Check == "37804012" || v64Check == "40123780") { v64.seek(0); QFile z64(saveFile); z64.open(QIODevice::WriteOnly); QByteArray data; QByteArray flipped; while (!v64.atEnd()) { data = v64.read(1024); if (v64Check == "37804012") { // byte-swapped for (int i = 0; i < data.size(); i+=2) { //Check to see if only one byte remaining (though byte count should always be even) if (i + 1 == data.size()) flipped.append(data[i]); else { flipped.append(data[i + 1]); flipped.append(data[i]); } } } else { // little-endian for (int i = 0; i < data.size(); i+=4) { //Check to see if less than 4 bytes remaining (though byte count should always be even) if (i + 3 > data.size()) { for (int n = data.size(); n >= i; n--) flipped.append(data[n]); } else { for (int n = i + 3; n >= i; n--) flipped.append(data[n]); } } } z64.write(flipped); flipped.truncate(0); } z64.close(); QMessageBox::information(parent, tr(" Converter").replace("",AppName), tr("Conversion complete!")); } else { message = "\"" + QFileInfo(v64).fileName() + "\" " + tr("is not a valid .v64 or .n64 file!"); QMessageBox::warning(parent, tr(" Converter").replace("",AppName), message); } v64.close(); } ./src/dialogs/settingsdialog.h0000664000175000017500000000774413623376647014742 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef SETTINGSDIALOG_H #define SETTINGSDIALOG_H #include class QCheckBox; class QComboBox; class QLabel; class QLineEdit; class QListWidget; namespace Ui { class SettingsDialog; } class SettingsDialog : public QDialog { Q_OBJECT public: explicit SettingsDialog(QWidget *parent = 0, int activeTab = 0); ~SettingsDialog(); private: void populateAvailable(bool downloadItems); Ui::SettingsDialog *ui; QList ctrlEnabled; QList ctrlAccessory; QList ctrlAccessoryLabel; QList ctrlMemPakLabel; QList ctrlTPakROMLabel; QList ctrlTPakSaveLabel; QList ctrlMemPak; QList ctrlTPakROM; QList ctrlTPakSave; QList ctrlMemPakButton; QList ctrlTPakROMButton; QList ctrlTPakSaveButton; QList downloadEnable; QList labelEnable; QList listCoverEnable; QList saveEnable; QList saveDisable; QStringList available; QStringList labelOptions; QStringList sortOptions; private slots: void addColumn(QListWidget *currentList, QListWidget *availableList); void addRomDirectory(); void browse64DD(); void browseBackground(); void browseCatalog(); void browseEmulator(); void browseEEPROM4k(); void browseEEPROM16k(); void browseFlashRAM(); void browseMemPak(); void browsePIF(); void browseSaves(); void browseSRAM(); void browseTPakROM(); void browseTPakSave(); void editSettings(); void hideBGTheme(QString imagePath); void listAddColumn(); void listRemoveColumn(); void listSortDown(); void listSortUp(); void removeColumn(QListWidget *currentList, QListWidget *availableList); void removeRomDirectory(); void populateTableAndListTab(bool downloadItems); void sortDown(QListWidget *currentList); void sortUp(QListWidget *currentList); void tableAddColumn(); void tableRemoveColumn(); void tableSortDown(); void tableSortUp(); void toggleAccessory(int index, int i = -1); void toggleController(bool active, int i = -1); void toggleDownload(bool active); void toggleGridColumn(bool active); void toggleLabel(bool active); void toggleListCover(bool active); void toggleSaves(bool active); void updateLanguageInfo(); }; #endif // SETTINGSDIALOG_H ./src/dialogs/logdialog.h0000664000175000017500000000364013623376647013652 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef LOGDIALOG_H #define LOGDIALOG_H #include class QDialogButtonBox; class QGridLayout; class QTextEdit; class LogDialog : public QDialog { Q_OBJECT public: explicit LogDialog(QString lastOutput, QWidget *parent = 0); private: QDialogButtonBox *logButtonBox; QGridLayout *logLayout; QTextEdit *logArea; }; #endif // LOGDIALOG_H ./src/dialogs/settingsdialog.cpp0000664000175000017500000011352513623376647015270 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "settingsdialog.h" #include "ui_settingsdialog.h" #include "../global.h" #include "../common.h" #include #include #include SettingsDialog::SettingsDialog(QWidget *parent, int activeTab) : QDialog(parent), ui(new Ui::SettingsDialog) { ui->setupUi(this); ui->tabWidget->setCurrentIndex(activeTab); //Populate Paths tab ui->emulatorPath->setText(SETTINGS.value("Paths/cen64", "").toString()); ui->pifPath->setText(SETTINGS.value("Paths/pifrom", "").toString()); ui->ddPath->setText(SETTINGS.value("Paths/ddiplrom", "").toString()); ui->catalogPath->setText(SETTINGS.value("Paths/catalog", "").toString()); QStringList romDirectories = SETTINGS.value("Paths/roms", "").toString().split("|"); romDirectories.removeAll(""); foreach (QString directory, romDirectories) ui->romList->addItem(directory); ui->savesPath->setText(SETTINGS.value("Saves/directory", "").toString()); ui->eeprom4kPath->setText(SETTINGS.value("Saves/eeprom4k", "").toString()); ui->eeprom16kPath->setText(SETTINGS.value("Saves/eeprom16k", "").toString()); ui->sramPath->setText(SETTINGS.value("Saves/sram", "").toString()); ui->flashPath->setText(SETTINGS.value("Saves/flash", "").toString()); //Widgets enabled when save checkbox is active saveEnable << ui->eeprom4kPathLabel << ui->eeprom4kPath << ui->eeprom4kButton << ui->eeprom16kPathLabel << ui->eeprom16kPath << ui->eeprom16kButton << ui->sramPathLabel << ui->sramPath << ui->sramButton << ui->flashPathLabel << ui->flashPath << ui->flashButton; //Widgets disabled when save checkbox is active saveDisable << ui->savesPathLabel << ui->savesPath << ui->savesButton; if (SETTINGS.value("Saves/individualsave", "").toString() == "true") { toggleSaves(true); ui->saveOption->setChecked(true); } else toggleSaves(false); connect(ui->emulatorButton, SIGNAL(clicked()), this, SLOT(browseEmulator())); connect(ui->pifButton, SIGNAL(clicked()), this, SLOT(browsePIF())); connect(ui->ddButton, SIGNAL(clicked()), this, SLOT(browse64DD())); connect(ui->catalogButton, SIGNAL(clicked()), this, SLOT(browseCatalog())); connect(ui->romAddButton, SIGNAL(clicked()), this, SLOT(addRomDirectory())); connect(ui->romRemoveButton, SIGNAL(clicked()), this, SLOT(removeRomDirectory())); connect(ui->savesButton, SIGNAL(clicked()), this, SLOT(browseSaves())); connect(ui->eeprom4kButton, SIGNAL(clicked()), this, SLOT(browseEEPROM4k())); connect(ui->eeprom16kButton, SIGNAL(clicked()), this, SLOT(browseEEPROM16k())); connect(ui->sramButton, SIGNAL(clicked()), this, SLOT(browseSRAM())); connect(ui->flashButton, SIGNAL(clicked()), this, SLOT(browseFlashRAM())); connect(ui->saveOption, SIGNAL(toggled(bool)), this, SLOT(toggleSaves(bool))); //Populate Emulation tab if (SETTINGS.value("Emulation/multithread", "").toString() == "true") ui->multithreadOption->setChecked(true); if (SETTINGS.value("Emulation/noaudio", "").toString() == "true") ui->noAudioOption->setChecked(true); if (SETTINGS.value("Emulation/novideo", "").toString() == "true") ui->noVideoOption->setChecked(true); //Populate Controllers tab ctrlEnabled << ui->ctrl1Enabled << ui->ctrl2Enabled << ui->ctrl3Enabled << ui->ctrl4Enabled; ctrlAccessory << ui->ctrl1Accessory << ui->ctrl2Accessory << ui->ctrl3Accessory << ui->ctrl4Accessory; ctrlAccessoryLabel << ui->ctrl1AccessoryLabel << ui->ctrl2AccessoryLabel << ui->ctrl3AccessoryLabel << ui->ctrl4AccessoryLabel; ctrlMemPak << ui->ctrl1MemPak << ui->ctrl2MemPak << ui->ctrl3MemPak << ui->ctrl4MemPak; ctrlMemPakButton << ui->ctrl1MemPakButton << ui->ctrl2MemPakButton << ui->ctrl3MemPakButton << ui->ctrl4MemPakButton; ctrlMemPakLabel << ui->ctrl1MemPakLabel << ui->ctrl2MemPakLabel << ui->ctrl3MemPakLabel << ui->ctrl4MemPakLabel; ctrlTPakROM << ui->ctrl1TPakROM << ui->ctrl2TPakROM << ui->ctrl3TPakROM << ui->ctrl4TPakROM; ctrlTPakROMButton << ui->ctrl1TPakROMButton << ui->ctrl2TPakROMButton << ui->ctrl3TPakROMButton << ui->ctrl4TPakROMButton; ctrlTPakROMLabel << ui->ctrl1TPakROMLabel << ui->ctrl2TPakROMLabel << ui->ctrl3TPakROMLabel << ui->ctrl4TPakROMLabel; ctrlTPakSave << ui->ctrl1TPakSave << ui->ctrl2TPakSave << ui->ctrl3TPakSave << ui->ctrl4TPakSave; ctrlTPakSaveButton << ui->ctrl1TPakSaveButton << ui->ctrl2TPakSaveButton << ui->ctrl3TPakSaveButton << ui->ctrl4TPakSaveButton; ctrlTPakSaveLabel << ui->ctrl1TPakSaveLabel << ui->ctrl2TPakSaveLabel << ui->ctrl3TPakSaveLabel << ui->ctrl4TPakSaveLabel; QStringList ctrlOptions; ctrlOptions << tr("None") << tr("Rumble Pak") << tr("Controller Pak") << tr("Transfer Pak"); for (int i = 0; i <= 3; i++) { QString ctrl = "Controller"+ QString::number(i + 1); ctrlAccessory.at(i)->insertItems(0, ctrlOptions); int ctrlAccessoryIndex = SETTINGS.value(ctrl+"/accessory", 0).toInt(); if (ctrlAccessoryIndex >= 0) ctrlAccessory.at(i)->setCurrentIndex(ctrlAccessoryIndex); toggleAccessory(ctrlAccessory.at(i)->currentIndex(), i); ctrlMemPak.at(i)->setText(SETTINGS.value(ctrl+"/mempak", "").toString()); ctrlTPakROM.at(i)->setText(SETTINGS.value(ctrl+"/tpakrom", "").toString()); ctrlTPakSave.at(i)->setText(SETTINGS.value(ctrl+"/tpaksave", "").toString()); if (SETTINGS.value(ctrl+"/enabled", "").toString() == "true") { ctrlEnabled.at(i)->setChecked(true); toggleController(true, i); } else toggleController(false, i); connect(ctrlEnabled.at(i), SIGNAL(toggled(bool)), this, SLOT(toggleController(bool))); connect(ctrlAccessory.at(i), SIGNAL(currentIndexChanged(int)), this, SLOT(toggleAccessory(int))); connect(ctrlMemPakButton.at(i), SIGNAL(clicked()), this, SLOT(browseMemPak())); connect(ctrlTPakROMButton.at(i), SIGNAL(clicked()), this, SLOT(browseTPakROM())); connect(ctrlTPakSaveButton.at(i), SIGNAL(clicked()), this, SLOT(browseTPakSave())); } //Populate Table tab int tableSizeIndex = 0; QString currentTableSize = SETTINGS.value("Table/imagesize","Medium").toString(); QList sizes; sizes << (QStringList() << tr("Extra Small") << "Extra Small") << (QStringList() << tr("Small") << "Small") << (QStringList() << tr("Medium") << "Medium") << (QStringList() << tr("Large") << "Large") << (QStringList() << tr("Extra Large") << "Extra Large") << (QStringList() << tr("Super") << "Super"); if (SETTINGS.value("Other/downloadinfo", "").toString() == "true") populateTableAndListTab(true); else populateTableAndListTab(false); if (SETTINGS.value("Table/stretchfirstcolumn", "true").toString() == "true") ui->tableStretchOption->setChecked(true); for (int i = 0; i < sizes.length(); i++) { ui->tableSizeBox->insertItem(i, sizes.at(i).at(0), sizes.at(i).at(1)); if (currentTableSize == sizes.at(i).at(1)) tableSizeIndex = i; } if (tableSizeIndex >= 0) ui->tableSizeBox->setCurrentIndex(tableSizeIndex); connect(ui->tableAddButton, SIGNAL(clicked()), this, SLOT(tableAddColumn())); connect(ui->tableRemoveButton, SIGNAL(clicked()), this, SLOT(tableRemoveColumn())); connect(ui->tableSortUpButton, SIGNAL(clicked()), this, SLOT(tableSortUp())); connect(ui->tableSortDownButton, SIGNAL(clicked()), this, SLOT(tableSortDown())); //Populate Grid tab int gridSizeIndex = 0, activeIndex = 0, inactiveIndex = 0, labelColorIndex = 0, bgThemeIndex = 0; QString currentGridSize = SETTINGS.value("Grid/imagesize","Medium").toString(); QString currentActiveColor = SETTINGS.value("Grid/activecolor","Cyan").toString(); QString currentInactiveColor = SETTINGS.value("Grid/inactivecolor","Black").toString(); QString currentLabelColor = SETTINGS.value("Grid/labelcolor","White").toString(); QString currentBGTheme = SETTINGS.value("Grid/theme","Normal").toString(); QList colors; colors << (QStringList() << tr("Black") << "Black") << (QStringList() << tr("White") << "White") << (QStringList() << tr("Light Gray") << "Light Gray") << (QStringList() << tr("Dark Gray") << "Dark Gray") << (QStringList() << tr("Green") << "Green") << (QStringList() << tr("Cyan") << "Cyan") << (QStringList() << tr("Blue") << "Blue") << (QStringList() << tr("Purple") << "Purple") << (QStringList() << tr("Red") << "Red") << (QStringList() << tr("Pink") << "Pink") << (QStringList() << tr("Orange") << "Orange") << (QStringList() << tr("Yellow") << "Yellow") << (QStringList() << tr("Brown") << "Brown"); QList bgThemes; bgThemes << (QStringList() << tr("Light") << "Light") << (QStringList() << tr("Normal") << "Normal") << (QStringList() << tr("Dark") << "Dark"); for (int i = 0; i < sizes.length(); i++) { ui->gridSizeBox->insertItem(i, sizes.at(i).at(0), sizes.at(i).at(1)); if (currentGridSize == sizes.at(i).at(1)) gridSizeIndex = i; } if (gridSizeIndex >= 0) ui->gridSizeBox->setCurrentIndex(gridSizeIndex); int gridColumnCount = SETTINGS.value("Grid/columncount","4").toInt(); ui->columnCountBox->setValue(gridColumnCount); if (SETTINGS.value("Grid/autocolumns", "true").toString() == "true") { toggleGridColumn(true); ui->autoColumnOption->setChecked(true); } else toggleGridColumn(false); for (int i = 0; i < colors.length(); i++) { ui->shadowActiveBox->insertItem(i, colors.at(i).at(0), colors.at(i).at(1)); if (currentActiveColor == colors.at(i).at(1)) activeIndex = i; ui->shadowInactiveBox->insertItem(i, colors.at(i).at(0), colors.at(i).at(1)); if (currentInactiveColor == colors.at(i).at(1)) inactiveIndex = i; ui->labelColorBox->insertItem(i, colors.at(i).at(0), colors.at(i).at(1)); if (currentLabelColor == colors.at(i).at(1)) labelColorIndex = i; } if (activeIndex >= 0) ui->shadowActiveBox->setCurrentIndex(activeIndex); if (inactiveIndex >= 0) ui->shadowInactiveBox->setCurrentIndex(inactiveIndex); if (labelColorIndex >= 0) ui->labelColorBox->setCurrentIndex(labelColorIndex); //Widgets to enable when label active labelEnable << ui->labelTextLabel << ui->labelTextBox << ui->labelColorLabel << ui->labelColorBox; if (SETTINGS.value("Grid/label", "true").toString() == "true") { toggleLabel(true); ui->labelOption->setChecked(true); } else toggleLabel(false); for (int i = 0; i < bgThemes.length(); i++) { ui->bgThemeBox->insertItem(i, bgThemes.at(i).at(0), bgThemes.at(i).at(1)); if (currentBGTheme == bgThemes.at(i).at(1)) bgThemeIndex = i; } if (bgThemeIndex >= 0) ui->bgThemeBox->setCurrentIndex(bgThemeIndex); QString imagePath = SETTINGS.value("Grid/background", "").toString(); ui->backgroundPath->setText(imagePath); hideBGTheme(imagePath); if (SETTINGS.value("Grid/sortdirection", "ascending").toString() == "descending") ui->gridDescendingOption->setChecked(true); connect(ui->autoColumnOption, SIGNAL(toggled(bool)), this, SLOT(toggleGridColumn(bool))); connect(ui->backgroundPath, SIGNAL(textChanged(QString)), this, SLOT(hideBGTheme(QString))); connect(ui->backgroundButton, SIGNAL(clicked()), this, SLOT(browseBackground())); connect(ui->labelOption, SIGNAL(toggled(bool)), this, SLOT(toggleLabel(bool))); //Populate List tab int listSizeIndex = 0, listTextIndex = 0, listThemeIndex = 0; QString currentListSize = SETTINGS.value("List/imagesize","Medium").toString(); QString currentListText = SETTINGS.value("List/textsize","Medium").toString(); QString currentListTheme = SETTINGS.value("List/theme","Light").toString(); QList themes; themes << (QStringList() << tr("Light") << "Light") << (QStringList() << tr("Dark") << "Dark"); listCoverEnable << ui->listSizeLabel << ui->listSizeBox; if (SETTINGS.value("List/displaycover", "").toString() == "true") { toggleListCover(true); ui->listCoverOption->setChecked(true); } else toggleListCover(false); if (SETTINGS.value("List/firstitemheader", "true").toString() == "true") ui->listHeaderOption->setChecked(true); for (int i = 0; i < sizes.length(); i++) { ui->listSizeBox->insertItem(i, sizes.at(i).at(0), sizes.at(i).at(1)); if (currentListSize == sizes.at(i).at(1)) listSizeIndex = i; ui->listTextBox->insertItem(i, sizes.at(i).at(0), sizes.at(i).at(1)); if (currentListText == sizes.at(i).at(1)) listTextIndex = i; } if (listSizeIndex >= 0) ui->listSizeBox->setCurrentIndex(listSizeIndex); if (listTextIndex >= 0) ui->listTextBox->setCurrentIndex(listTextIndex); for (int i = 0; i < themes.length(); i++) { ui->listThemeBox->insertItem(i, themes.at(i).at(0), themes.at(i).at(1)); if (currentListTheme == themes.at(i).at(1)) listThemeIndex = i; } if (listThemeIndex >= 0) ui->listThemeBox->setCurrentIndex(listThemeIndex); if (SETTINGS.value("List/sortdirection", "ascending").toString() == "descending") ui->listDescendingOption->setChecked(true); connect(ui->listCoverOption, SIGNAL(toggled(bool)), this, SLOT(toggleListCover(bool))); connect(ui->listAddButton, SIGNAL(clicked()), this, SLOT(listAddColumn())); connect(ui->listRemoveButton, SIGNAL(clicked()), this, SLOT(listRemoveColumn())); connect(ui->listSortUpButton, SIGNAL(clicked()), this, SLOT(listSortUp())); connect(ui->listSortDownButton, SIGNAL(clicked()), this, SLOT(listSortDown())); //Populate Other tab int languageIndex = 0; QString currentLanguage = SETTINGS.value("language", getDefaultLanguage()).toString(); QList languages; languages << (QStringList() << QString::fromUtf8("English") << "EN") << (QStringList() << QString::fromUtf8("Français") << "FR") << (QStringList() << QString::fromUtf8("Русский") << "RU"); downloadEnable << ui->tableSizeLabel << ui->tableSizeBox << ui->listCoverOption << ui->listSizeLabel << ui->listSizeBox; if (SETTINGS.value("Other/downloadinfo", "").toString() == "true") { toggleDownload(true); ui->downloadOption->setChecked(true); } else toggleDownload(false); if (SETTINGS.value("Other/consoleoutput", "").toString() == "true") ui->outputOption->setChecked(true); #ifdef Q_OS_WIN ui->outputLabel->setVisible(false); ui->outputOption->setVisible(false); #endif ui->parametersLine->setText(SETTINGS.value("Other/parameters", "").toString()); for (int i = 0; i < languages.length(); i++) { ui->languageBox->insertItem(i, languages.at(i).at(0), languages.at(i).at(1)); if (currentLanguage == languages.at(i).at(1)) languageIndex = i; } ui->languageBox->setCurrentIndex(languageIndex); ui->languageInfoLabel->setHidden(true); connect(ui->downloadOption, SIGNAL(toggled(bool)), this, SLOT(toggleDownload(bool))); connect(ui->downloadOption, SIGNAL(toggled(bool)), this, SLOT(populateTableAndListTab(bool))); connect(ui->languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateLanguageInfo())); ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(editSettings())); connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject())); } SettingsDialog::~SettingsDialog() { delete ui; } void SettingsDialog::addColumn(QListWidget *currentList, QListWidget *availableList) { int row = availableList->currentRow(); if (row >= 0) currentList->addItem(availableList->takeItem(row)); } void SettingsDialog::addRomDirectory() { QString path = QFileDialog::getExistingDirectory(this, tr("ROM Directory")); if (path != "") { //check for duplicates bool found = false; foreach (QListWidgetItem *item, ui->romList->findItems("*", Qt::MatchWildcard)) if (path == item->text()) found = true; if (!found) ui->romList->addItem(path); } } void SettingsDialog::browse64DD() { QString path = QFileDialog::getOpenFileName(this, tr("64DD IPL ROM File")); if (path != "") ui->ddPath->setText(path); } void SettingsDialog::browseBackground() { QString path = QFileDialog::getOpenFileName(this, tr("Background Image")); if (path != "") ui->backgroundPath->setText(path); } void SettingsDialog::browseCatalog() { QString path = QFileDialog::getOpenFileName(this, tr("Catalog File")); if (path != "") ui->catalogPath->setText(path); } void SettingsDialog::browseEmulator() { QString path = QFileDialog::getOpenFileName(this, tr(" Executable") .replace("",ParentName)); if (path != "") ui->emulatorPath->setText(path); } void SettingsDialog::browseEEPROM4k() { QString path = QFileDialog::getOpenFileName(this, tr("4kbit EEPROM File")); if (path != "") ui->eeprom4kPath->setText(path); } void SettingsDialog::browseEEPROM16k() { QString path = QFileDialog::getOpenFileName(this, tr("16kbit EEPROM File")); if (path != "") ui->eeprom16kPath->setText(path); } void SettingsDialog::browseFlashRAM() { QString path = QFileDialog::getOpenFileName(this, tr("FlashRAM File")); if (path != "") ui->flashPath->setText(path); } void SettingsDialog::browseMemPak() { QString path = QFileDialog::getOpenFileName(this, tr("Controller Pak File")); if (path != "") ctrlMemPak.at(ui->controllersTabWidget->currentIndex())->setText(path); } void SettingsDialog::browsePIF() { QString path = QFileDialog::getOpenFileName(this, tr("PIF IPL ROM File")); if (path != "") ui->pifPath->setText(path); } void SettingsDialog::browseSaves() { QString path = QFileDialog::getExistingDirectory(this, tr("Save Directory")); if (path != "") ui->savesPath->setText(path); } void SettingsDialog::browseSRAM() { QString path = QFileDialog::getOpenFileName(this, tr("SRAM File")); if (path != "") ui->sramPath->setText(path); } void SettingsDialog::browseTPakROM() { QString path = QFileDialog::getOpenFileName(this, tr("Transfer Pak ROM File")); if (path != "") ctrlTPakROM.at(ui->controllersTabWidget->currentIndex())->setText(path); } void SettingsDialog::browseTPakSave() { QString path = QFileDialog::getOpenFileName(this, tr("Transfer Pak Save File")); if (path != "") ctrlTPakSave.at(ui->controllersTabWidget->currentIndex())->setText(path); } void SettingsDialog::editSettings() { //Set download option first if (ui->downloadOption->isChecked()) { SETTINGS.setValue("Other/downloadinfo", true); populateAvailable(true); //This removes thegamesdb.net options if user unselects download } else { SETTINGS.setValue("Other/downloadinfo", ""); populateAvailable(false); } //Paths tab SETTINGS.setValue("Paths/cen64", ui->emulatorPath->text()); SETTINGS.setValue("Paths/pifrom", ui->pifPath->text()); SETTINGS.setValue("Paths/ddiplrom", ui->ddPath->text()); SETTINGS.setValue("Paths/catalog", ui->catalogPath->text()); SETTINGS.setValue("Saves/directory", ui->savesPath->text()); SETTINGS.setValue("Saves/eeprom4k", ui->eeprom4kPath->text()); SETTINGS.setValue("Saves/eeprom16k", ui->eeprom16kPath->text()); SETTINGS.setValue("Saves/sram", ui->sramPath->text()); SETTINGS.setValue("Saves/flash", ui->flashPath->text()); QStringList romDirectories; foreach (QListWidgetItem *item, ui->romList->findItems("*", Qt::MatchWildcard)) romDirectories << item->text(); SETTINGS.setValue("Paths/roms", romDirectories.join("|")); if (ui->saveOption->isChecked()) SETTINGS.setValue("Saves/individualsave", true); else SETTINGS.setValue("Saves/individualsave", ""); //Emulation tab if (ui->multithreadOption->isChecked()) SETTINGS.setValue("Emulation/multithread", true); else SETTINGS.setValue("Emulation/multithread", ""); if (ui->noAudioOption->isChecked()) SETTINGS.setValue("Emulation/noaudio", true); else SETTINGS.setValue("Emulation/noaudio", ""); if (ui->noVideoOption->isChecked()) SETTINGS.setValue("Emulation/novideo", true); else SETTINGS.setValue("Emulation/novideo", ""); //Controllers tab for (int i = 0; i <= 3; i++) { QString ctrl = "Controller"+QString::number(i + 1); if (ctrlEnabled.at(i)->isChecked()) SETTINGS.setValue(ctrl+"/enabled", true); else SETTINGS.setValue(ctrl+"/enabled", ""); SETTINGS.setValue(ctrl+"/accessory", ctrlAccessory.at(i)->currentIndex()); SETTINGS.setValue(ctrl+"/mempak", ctrlMemPak.at(i)->text()); SETTINGS.setValue(ctrl+"/tpakrom", ctrlTPakROM.at(i)->text()); SETTINGS.setValue(ctrl+"/tpaksave", ctrlTPakSave.at(i)->text()); } //Table tab QStringList tableVisibleItems; foreach (QListWidgetItem *item, ui->tableCurrentList->findItems("*", Qt::MatchWildcard)) if (available.contains(item->data(Qt::UserRole).toString())) tableVisibleItems << item->data(Qt::UserRole).toString(); SETTINGS.setValue("Table/columns", tableVisibleItems.join("|")); if (ui->tableStretchOption->isChecked()) SETTINGS.setValue("Table/stretchfirstcolumn", true); else SETTINGS.setValue("Table/stretchfirstcolumn", ""); SETTINGS.setValue("Table/imagesize", ui->tableSizeBox->itemData(ui->tableSizeBox->currentIndex())); //Grid tab SETTINGS.setValue("Grid/imagesize", ui->gridSizeBox->itemData(ui->gridSizeBox->currentIndex())); SETTINGS.setValue("Grid/columncount", ui->columnCountBox->value()); if (ui->autoColumnOption->isChecked()) SETTINGS.setValue("Grid/autocolumns", true); else SETTINGS.setValue("Grid/autocolumns", ""); SETTINGS.setValue("Grid/inactivecolor", ui->shadowInactiveBox->itemData(ui->shadowInactiveBox->currentIndex())); SETTINGS.setValue("Grid/activecolor", ui->shadowActiveBox->itemData(ui->shadowActiveBox->currentIndex())); SETTINGS.setValue("Grid/theme", ui->bgThemeBox->itemData(ui->bgThemeBox->currentIndex())); SETTINGS.setValue("Grid/background", ui->backgroundPath->text()); if (ui->labelOption->isChecked()) SETTINGS.setValue("Grid/label", true); else SETTINGS.setValue("Grid/label", ""); SETTINGS.setValue("Grid/labeltext", ui->labelTextBox->itemData(ui->labelTextBox->currentIndex())); SETTINGS.setValue("Grid/labelcolor", ui->labelColorBox->itemData(ui->labelColorBox->currentIndex())); SETTINGS.setValue("Grid/sort", ui->gridSortBox->itemData(ui->gridSortBox->currentIndex())); if (ui->gridDescendingOption->isChecked()) SETTINGS.setValue("Grid/sortdirection", "descending"); else SETTINGS.setValue("Grid/sortdirection", "ascending"); //List tab QStringList listVisibleItems; foreach (QListWidgetItem *item, ui->listCurrentList->findItems("*", Qt::MatchWildcard)) if (available.contains(item->data(Qt::UserRole).toString())) listVisibleItems << item->data(Qt::UserRole).toString(); SETTINGS.setValue("List/columns", listVisibleItems.join("|")); if (ui->listHeaderOption->isChecked()) SETTINGS.setValue("List/firstitemheader", true); else SETTINGS.setValue("List/firstitemheader", ""); if (ui->listCoverOption->isChecked() && ui->downloadOption->isChecked()) SETTINGS.setValue("List/displaycover", true); else SETTINGS.setValue("List/displaycover", ""); SETTINGS.setValue("List/imagesize", ui->listSizeBox->itemData(ui->listSizeBox->currentIndex())); SETTINGS.setValue("List/textsize", ui->listTextBox->itemData(ui->listTextBox->currentIndex())); SETTINGS.setValue("List/theme", ui->listThemeBox->itemData(ui->listThemeBox->currentIndex())); SETTINGS.setValue("List/sort", ui->listSortBox->itemData(ui->listSortBox->currentIndex())); if (ui->listDescendingOption->isChecked()) SETTINGS.setValue("List/sortdirection", "descending"); else SETTINGS.setValue("List/sortdirection", "ascending"); //Other tab #ifndef Q_OS_WIN if (ui->outputOption->isChecked()) SETTINGS.setValue("Other/consoleoutput", true); else SETTINGS.setValue("Other/consoleoutput", ""); #endif SETTINGS.setValue("Other/parameters", ui->parametersLine->text()); SETTINGS.setValue("language", ui->languageBox->itemData(ui->languageBox->currentIndex())); close(); } void SettingsDialog::hideBGTheme(QString imagePath) { if (imagePath == "") { ui->bgThemeLabel->setEnabled(true); ui->bgThemeBox->setEnabled(true); } else { ui->bgThemeLabel->setEnabled(false); ui->bgThemeBox->setEnabled(false); } } void SettingsDialog::listAddColumn() { addColumn(ui->listCurrentList, ui->listAvailableList); } void SettingsDialog::listRemoveColumn() { removeColumn(ui->listCurrentList, ui->listAvailableList); } void SettingsDialog::listSortDown() { sortDown(ui->listCurrentList); } void SettingsDialog::listSortUp() { sortUp(ui->listCurrentList); } void SettingsDialog::populateAvailable(bool downloadItems) { available.clear(); labelOptions.clear(); sortOptions.clear(); available << "Filename" << "Filename (extension)" << "Zip File" << "GoodName" << "Internal Name" << "Size" << "MD5" << "CRC1" << "CRC2" << "Players" << "Rumble" << "Save Type"; labelOptions << "Filename" << "Filename (extension)" << "GoodName" << "Internal Name"; sortOptions << "Filename" << "GoodName" << "Internal Name" << "Size"; if (downloadItems) { available << "Game Title" << "Release Date" << "Overview" << "ESRB" << "Genre" << "Publisher" << "Developer" << "Game Cover"; labelOptions << "Game Title" << "Release Date" << "Genre"; sortOptions << "Game Title" << "Release Date" << "ESRB" << "Genre" << "Publisher" << "Developer"; } available.sort(); labelOptions.sort(); sortOptions.sort(); } void SettingsDialog::populateTableAndListTab(bool downloadItems) { populateAvailable(downloadItems); //Table columns and sort fields QStringList tableCurrent, tableAvailable; tableCurrent = SETTINGS.value("Table/columns", "Filename|Size").toString().split("|"); tableAvailable = available; foreach (QString cur, tableCurrent) { if (tableAvailable.contains(cur)) tableAvailable.removeOne(cur); else //Someone added an invalid item tableCurrent.removeOne(cur); } ui->tableAvailableList->clear(); foreach (QString listItem, tableAvailable) { QListWidgetItem *item = new QListWidgetItem; item->setText(getTranslation(listItem)); item->setData(Qt::UserRole, listItem); ui->tableAvailableList->addItem(item); } ui->tableAvailableList->sortItems(); ui->tableCurrentList->clear(); foreach (QString listItem, tableCurrent) { QListWidgetItem *item = new QListWidgetItem; item->setText(getTranslation(listItem)); item->setData(Qt::UserRole, listItem); ui->tableCurrentList->addItem(item); } //Grid sort field and label text int labelTextIndex = 0, gridSortIndex = 0; QString currentLabelText = SETTINGS.value("Grid/labeltext","Filename").toString(); QString currentGridSort = SETTINGS.value("Grid/sort","Filename").toString(); ui->labelTextBox->clear(); for (int i = 0; i < labelOptions.length(); i++) { ui->labelTextBox->insertItem(i, getTranslation(labelOptions.at(i)), labelOptions.at(i)); if (currentLabelText == labelOptions.at(i)) labelTextIndex = i; } if (labelTextIndex >= 0) ui->labelTextBox->setCurrentIndex(labelTextIndex); ui->gridSortBox->clear(); for (int i = 0; i < sortOptions.length(); i++) { ui->gridSortBox->insertItem(i, getTranslation(sortOptions.at(i)), sortOptions.at(i)); if (currentGridSort == sortOptions.at(i)) gridSortIndex = i; } if (gridSortIndex >= 0) ui->gridSortBox->setCurrentIndex(gridSortIndex); //List items and sort field QStringList listCurrent, listAvailable; listCurrent = SETTINGS.value("List/columns", "Filename|Internal Name|Size").toString().split("|"); listAvailable = available; listAvailable.removeOne("Game Cover"); //Game Cover handled separately foreach (QString cur, listCurrent) { if (listAvailable.contains(cur)) listAvailable.removeOne(cur); else //Someone added an invalid item listCurrent.removeOne(cur); } ui->listAvailableList->clear(); foreach (QString listItem, listAvailable) { QListWidgetItem *item = new QListWidgetItem; item->setText(getTranslation(listItem)); item->setData(Qt::UserRole, listItem); ui->listAvailableList->addItem(item); } ui->listAvailableList->sortItems(); ui->listCurrentList->clear(); foreach (QString listItem, listCurrent) { QListWidgetItem *item = new QListWidgetItem; item->setText(getTranslation(listItem)); item->setData(Qt::UserRole, listItem); ui->listCurrentList->addItem(item); } int listSortIndex = 0; QString currentListSort = SETTINGS.value("List/sort","Filename").toString(); ui->listSortBox->clear(); for (int i = 0; i < sortOptions.length(); i++) { ui->listSortBox->insertItem(i, getTranslation(sortOptions.at(i)), sortOptions.at(i)); if (currentListSort == sortOptions.at(i)) listSortIndex = i; } if (listSortIndex >= 0) ui->listSortBox->setCurrentIndex(listSortIndex); } void SettingsDialog::removeColumn(QListWidget *currentList, QListWidget *availableList) { int row = currentList->currentRow(); if (row >= 0) { availableList->addItem(currentList->takeItem(row)); availableList->sortItems(); } } void SettingsDialog::removeRomDirectory() { int row = ui->romList->currentRow(); if (row >= 0) delete ui->romList->takeItem(row); } void SettingsDialog::sortDown(QListWidget *currentList) { int row = currentList->currentRow(); if (row > 0) { QListWidgetItem *item = currentList->takeItem(row); currentList->insertItem(row - 1, item); currentList->setCurrentRow(row - 1); } } void SettingsDialog::sortUp(QListWidget *currentList) { int row = currentList->currentRow(); if (row >= 0 && row < currentList->count() - 1) { QListWidgetItem *item = currentList->takeItem(row); currentList->insertItem(row + 1, item); currentList->setCurrentRow(row + 1); } } void SettingsDialog::tableAddColumn() { addColumn(ui->tableCurrentList, ui->tableAvailableList); } void SettingsDialog::tableRemoveColumn() { removeColumn(ui->tableCurrentList, ui->tableAvailableList); } void SettingsDialog::tableSortDown() { sortDown(ui->tableCurrentList); } void SettingsDialog::tableSortUp() { sortUp(ui->tableCurrentList); } void SettingsDialog::toggleAccessory(int index, int i) { int ctrl; if (i >= 0 && i <= 3) ctrl = i; else ctrl = ui->controllersTabWidget->currentIndex(); QList memPakEnable; memPakEnable << ctrlMemPak.at(ctrl) << ctrlMemPakButton.at(ctrl) << ctrlMemPakLabel.at(ctrl); QList tPakEnable; tPakEnable << ctrlTPakROM.at(ctrl) << ctrlTPakROMButton.at(ctrl) << ctrlTPakROMLabel.at(ctrl) << ctrlTPakSave.at(ctrl) << ctrlTPakSaveButton.at(ctrl) << ctrlTPakSaveLabel.at(ctrl); QList allEnable; allEnable << memPakEnable << tPakEnable; foreach (QWidget *next, allEnable) next->setEnabled(false); if (index == 2) //Controller Pak foreach (QWidget *next, memPakEnable) next->setEnabled(true); else if (index == 3) //Transfer Pak foreach (QWidget *next, tPakEnable) next->setEnabled(true); } void SettingsDialog::toggleController(bool active, int i) { int ctrl; if (i >= 0 && i <= 3) ctrl = i; else ctrl = ui->controllersTabWidget->currentIndex(); QList cEnable; if (active) cEnable << ctrlAccessory.at(ctrl); else cEnable << ctrlAccessory.at(ctrl) << ctrlAccessoryLabel.at(ctrl) << ctrlMemPak.at(ctrl) << ctrlMemPakButton.at(ctrl) << ctrlMemPakLabel.at(ctrl) << ctrlTPakROM.at(ctrl) << ctrlTPakROMButton.at(ctrl) << ctrlTPakROMLabel.at(ctrl) << ctrlTPakSave.at(ctrl) << ctrlTPakSaveButton.at(ctrl) << ctrlTPakSaveLabel.at(ctrl); foreach (QWidget *next, cEnable) next->setEnabled(active); if (active) toggleAccessory(ctrlAccessory.at(ctrl)->currentIndex(), ctrl); } void SettingsDialog::toggleDownload(bool active) { foreach (QWidget *next, downloadEnable) next->setEnabled(active); if (active) toggleListCover(ui->listCoverOption->isChecked()); } void SettingsDialog::toggleGridColumn(bool active) { if (active) ui->columnCountBox->setEnabled(false); else ui->columnCountBox->setEnabled(true); } void SettingsDialog::toggleLabel(bool active) { foreach (QWidget *next, labelEnable) next->setEnabled(active); } void SettingsDialog::toggleListCover(bool active) { foreach (QWidget *next, listCoverEnable) next->setEnabled(active); } void SettingsDialog::toggleSaves(bool active) { foreach (QWidget *next, saveEnable) next->setEnabled(active); foreach (QWidget *next, saveDisable) next->setEnabled(!active); } void SettingsDialog::updateLanguageInfo() { ui->languageInfoLabel->setHidden(false); const char *sourceText = "Note: Language changes will not take place until application restart."; QTranslator translator; QString language = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString().toLower(); QString resource = ":/locale/"+AppNameLower+"_"+language+".qm"; if (QFileInfo(resource).exists()) { translator.load(resource); ui->languageInfoLabel->setText(translator.translate("SettingsDialog", sourceText)); } else ui->languageInfoLabel->setText(sourceText); } ./src/dialogs/aboutdialog.cpp0000664000175000017500000000713013623376647014534 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "aboutdialog.h" #include "../global.h" #include "../common.h" #include #include #include #include AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(tr("About ").replace("",AppName)); setMinimumSize(600, 300); aboutLayout = new QGridLayout(this); icon = new QLabel(this); icon->setPixmap(QPixmap(":/images/"+ParentNameLower+".png")); QFile licenseFile(":/other/LICENSE"); licenseFile.open(QIODevice::ReadOnly); license = new QPlainTextEdit(licenseFile.readAll(), this); license->setReadOnly(true); licenseFile.close(); QString description = ""+AppName+"
" + tr("Version") + " " + getVersion() + "

" + tr("A customizable launcher for using Qt.") .replace("",ParentName); QString website = "" + tr(" website").replace("",ParentName) + ""; QString github = "" + tr("GitHub repository") + ""; descriptionLabel = new QLabel(description, this); copyrightLabel = new QLabel(Copyright, this); websiteLink = new QLabel(website, this); githubLink = new QLabel(github, this); websiteLink->setOpenExternalLinks(true); githubLink->setOpenExternalLinks(true); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, Qt::Horizontal, this); aboutLayout->addWidget(icon, 0, 0, 4, 1); aboutLayout->addWidget(descriptionLabel, 0, 1); aboutLayout->addWidget(license, 1, 1); aboutLayout->addWidget(copyrightLabel, 2, 1); aboutLayout->addWidget(websiteLink, 3, 1); aboutLayout->addWidget(githubLink, 4, 1); aboutLayout->addWidget(buttonBox, 5, 1); aboutLayout->setColumnStretch(1, 1); aboutLayout->setRowStretch(1, 1); aboutLayout->setColumnMinimumWidth(0, 150); connect(buttonBox, SIGNAL(accepted()), this, SLOT(close())); setLayout(aboutLayout); } ./src/dialogs/aboutdialog.h0000664000175000017500000000405313623376647014202 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef ABOUTDIALOG_H #define ABOUTDIALOG_H #include class QDialogButtonBox; class QGridLayout; class QLabel; class QPlainTextEdit; class AboutDialog : public QDialog { Q_OBJECT public: explicit AboutDialog(QWidget *parent = 0); private: QDialogButtonBox *buttonBox; QGridLayout *aboutLayout; QLabel *copyrightLabel; QLabel *descriptionLabel; QLabel *githubLink; QLabel *websiteLink; QLabel *icon; QPlainTextEdit *license; }; #endif // ABOUTDIALOG_H ./src/dialogs/logdialog.cpp0000664000175000017500000000477313623376647014215 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "logdialog.h" #include "../global.h" #include #include #include #include LogDialog::LogDialog(QString lastOutput, QWidget *parent) : QDialog(parent) { setWindowTitle(tr(" Log").replace("",ParentName)); setMinimumSize(600, 400); logLayout = new QGridLayout(this); logLayout->setContentsMargins(5, 10, 5, 10); logArea = new QTextEdit(this); logArea->setWordWrapMode(QTextOption::NoWrap); QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); logArea->setFont(font); logArea->setPlainText(lastOutput); logButtonBox = new QDialogButtonBox(Qt::Horizontal, this); logButtonBox->addButton(tr("Close"), QDialogButtonBox::AcceptRole); logLayout->addWidget(logArea, 0, 0); logLayout->addWidget(logButtonBox, 1, 0); connect(logButtonBox, SIGNAL(accepted()), this, SLOT(close())); setLayout(logLayout); } ./src/dialogs/v64converter.h0000664000175000017500000000354713623376647014266 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef V64CONVERTER_H #define V64CONVERTER_H #include class V64Converter : public QObject { Q_OBJECT public: explicit V64Converter(QString romPath, QWidget *parent = 0); private: void runConverter(QString v64File, QString saveFile, QWidget *parent = 0); }; #endif // V64CONVERTER_H ./src/main.cpp0000664000175000017500000000546213623376647011552 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "global.h" #include "common.h" #include "mainwindow.h" #include #include #include #include #include int main(int argc, char *argv[]) { QApplication application(argc, argv); QTranslator translator; QString language = SETTINGS.value("language", getDefaultLanguage()).toString(); if (language != "EN") { QString resource = ":/locale/"+AppNameLower+"_"+language.toLower()+".qm"; if (QFileInfo(resource).exists()) { translator.load(resource); application.installTranslator(&translator); } } QCoreApplication::setOrganizationName(ParentName); QCoreApplication::setApplicationName(AppName); MainWindow window; QString maximized = SETTINGS.value("Geometry/maximized", "").toString(); QString windowx = SETTINGS.value("Geometry/windowx", "").toString(); QString windowy = SETTINGS.value("Geometry/windowy", "").toString(); if (maximized == "true") { window.showMaximized(); } else { window.show(); } if (windowx == "" && windowy == "") { window.move(QGuiApplication::primaryScreen()->geometry().center() - window.rect().center()); } return application.exec(); } ./src/common.h0000664000175000017500000000576413623376647011570 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef COMMON_H #define COMMON_H #include #include #include class QColor; class QSize; struct Rom { QString fileName; QString directory; QString romMD5; QString internalName; QString zipFile; QString baseName; QString size; int sortSize; QString goodName; QString CRC1; QString CRC2; QString players; QString saveType; QString rumble; QString gameTitle; QString releaseDate; QString sortDate; QString overview; QString esrb; QString genre; QString publisher; QString developer; QString rating; QPixmap image; int count; bool imageExists; }; bool romSorter(const Rom &firstRom, const Rom &lastRom); int getDefaultWidth(QString id, int imageWidth); int getGridSize(QString which); int getTableDataIndexFromName(QString infoName); int getTextSize(); QByteArray byteswap(QByteArray romData); QStringList getZippedFiles(QString completeFileName); QByteArray *getZippedRom(QString romFileName, QString zipFile); QColor getColor(QString color, int transparency = 255); QString getDefaultLanguage(); QString getTranslation(QString text); QGraphicsDropShadowEffect *getShadow(bool active); QSize getImageSize(QString view); QString getCacheLocation(); QString getDataLocation(); QString getRomInfo(QString identifier, const Rom *rom, bool removeWarn = false, bool sort = false); QString getVersion(); #endif // COMMON_H ./src/emulation/0000755000175000017500000000000013623376647012106 5ustar dandan./src/emulation/emulatorhandler.cpp0000664000175000017500000003703513623376647016012 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #include "emulatorhandler.h" #include "../global.h" #include "../common.h" #include #include #include #include #include #include EmulatorHandler::EmulatorHandler(QWidget *parent) : QObject(parent) { this->parent = parent; lastOutput = ""; } void EmulatorHandler::checkStatus(int status) { if (status > 0) { QMessageBox exitDialog(parent); exitDialog.setWindowTitle(tr("Warning")); exitDialog.setText(tr(" quit unexpectedly. Check the log for more information.") .replace("",ParentName)); exitDialog.setIcon(QMessageBox::Warning); exitDialog.addButton(QMessageBox::Ok); exitDialog.addButton(tr("View Log..."), QMessageBox::HelpRole); int ret = exitDialog.exec(); if (ret == 0) emit showLog(); } updateStatus(tr("Emulation stopped"), 3000); } void EmulatorHandler::cleanTemp() { QFile::remove(QDir::tempPath() + "/" + AppNameLower + "/" + qgetenv("USER") + "/temp.bin"); QFile::remove(QDir::tempPath() + "/" + AppNameLower + "/" + qgetenv("USER") + "/64dd-temp.bin"); } void EmulatorHandler::emitFinished() { emit finished(); } QStringList EmulatorHandler::parseArgString(QString argString) { QStringList result; QString arg; bool inQuote = false; bool inApos = false; for (int i = 0; i < argString.size(); i++) { // Check if inside of a quote if (argString.at(i) == QLatin1Char('"')) { inQuote = !inQuote; // Only continue if outside of both quotes and apostrophes if (arg.isEmpty() || (!inQuote && !inApos)) continue; } // Same check for apostrophes if (argString.at(i) == QLatin1Char('\'')) { inApos = !inApos; if (arg.isEmpty() || (!inQuote && !inApos)) continue; } if (!inQuote && !inApos && argString.at(i).isSpace()) { if (!arg.isEmpty()) { result += arg; arg.clear(); } } else arg += argString.at(i); } if (!arg.isEmpty()) result += arg; return result; } void EmulatorHandler::readOutput() { QString output = emulatorProc->readAllStandardOutput(); QStringList outputList = output.split("\n"); int lastIndex = outputList.lastIndexOf(QRegExp("^.*VI/s.*MHz$")); if (lastIndex >= 0) updateStatus(outputList[lastIndex]); lastOutput.append(output); } void EmulatorHandler::startEmulator(QDir romDir, QString romFileName, QString zipFileName, QDir ddDir, QString ddFileName, QString ddZipName) { QString completeRomPath = "", complete64DDPath = ""; bool zip = false, ddZip = false; //If zipped file, extract and write to temp location for loading QStringList zippedFiles; zippedFiles << zipFileName << ddZipName; bool ddZipCheck = false; foreach (QString zippedFile, zippedFiles) { if (zippedFile != "") { QString fileInZip, tempName, romPath, zipFile; if (!ddZipCheck) { zip = true; tempName = "/temp.bin"; fileInZip = romFileName; zipFile = romDir.absoluteFilePath(zippedFile); } else { ddZip = true; tempName = "/64dd-temp.bin"; fileInZip = ddFileName; zipFile = ddDir.absoluteFilePath(zippedFile); } QByteArray *romData = getZippedRom(fileInZip, zipFile); QString tempDir = QDir::tempPath() + "/" + AppNameLower + "/" + qgetenv("USER"); QDir().mkpath(tempDir); romPath = tempDir + tempName; QFile tempRom(romPath); tempRom.open(QIODevice::WriteOnly); tempRom.write(*romData); tempRom.close(); delete romData; if (!ddZipCheck) completeRomPath = romPath; else complete64DDPath = romPath; } ddZipCheck = true; } if (zipFileName == "" && romFileName != "") completeRomPath = romDir.absoluteFilePath(romFileName); if (ddZipName == "" && ddFileName != "") complete64DDPath = ddDir.absoluteFilePath(ddFileName); QString emulatorPath = SETTINGS.value("Paths/cen64", "").toString(); QString pifPath = SETTINGS.value("Paths/pifrom", "").toString(); QString ddIPLPath = SETTINGS.value("Paths/ddiplrom", "").toString(); bool ddMode = false; if (SETTINGS.value("Emulation/64dd", "").toString() == "true") ddMode = true; QFile emulatorFile(emulatorPath); QFile pifFile(pifPath); QFile ddIPL(ddIPLPath); QFile romFile(completeRomPath); QFile ddFile(complete64DDPath); //Sanity checks if (!emulatorFile.exists() || QFileInfo(emulatorFile).isDir() || !QFileInfo(emulatorFile).isExecutable()) { QMessageBox::warning(parent, tr("Warning"), tr(" executable not found.").replace("",ParentName)); if (zip || ddZip) cleanTemp(); return; } if (!pifFile.exists() || QFileInfo(pifFile).isDir()) { QMessageBox::warning(parent, tr("Warning"), tr("PIF IPL file not found.")); if (zip || ddZip) cleanTemp(); return; } if (ddIPLPath != "" && (!ddIPL.exists() || QFileInfo(ddIPL).isDir())) { QMessageBox::warning(parent, tr("Warning"), tr("64DD IPL file not found.")); if (zip || ddZip) cleanTemp(); return; } if (completeRomPath != "" && (!romFile.exists() || QFileInfo(romFile).isDir())) { QMessageBox::warning(parent, tr("Warning"), tr("ROM file not found.")); if (zip || ddZip) cleanTemp(); return; } if (completeRomPath == "" && complete64DDPath != "" && (!ddFile.exists() || QFileInfo(ddFile).isDir())) { QMessageBox::warning(parent, tr("Warning"), tr("64DD ROM file not found.")); if (zip || ddZip) cleanTemp(); return; } if (completeRomPath == "" && complete64DDPath == "") { QMessageBox::warning(parent, tr("Warning"), tr("No ROM selected.")); if (zip || ddZip) cleanTemp(); return; } if (completeRomPath != "") { romFile.open(QIODevice::ReadOnly); QByteArray romCheck = romFile.read(4); romFile.close(); if (romCheck.toHex() != "80371240") { if (romCheck.toHex() == "e848d316") { // 64DD file instead if (complete64DDPath == "" && ddMode) { complete64DDPath = completeRomPath; ddFile.setFileName(complete64DDPath); completeRomPath = ""; } else { QMessageBox::warning(parent, tr("Warning"), tr("64DD not enabled.")); if (zip || ddZip) cleanTemp(); return; } } else { QMessageBox::warning(parent, tr("Warning"), tr("Not a valid Z64 File.")); if (zip || ddZip) cleanTemp(); return; } } } if (complete64DDPath != "" && ddMode) { ddFile.open(QIODevice::ReadOnly); QByteArray romCheck = ddFile.read(4); ddFile.close(); if (romCheck.toHex() != "e848d316") { QMessageBox::warning(parent, tr("Warning"), tr("Not a valid 64DD File.")); if (zip || ddZip) cleanTemp(); return; } } QStringList args; if (SETTINGS.value("Saves/individualsave", "").toString() == "true") { QString eeprom4kPath = SETTINGS.value("Saves/eeprom4k", "").toString(); QString eeprom16kPath = SETTINGS.value("Saves/eeprom16k", "").toString(); QString sramPath = SETTINGS.value("Saves/sram", "").toString(); QString flashPath = SETTINGS.value("Saves/flash", "").toString(); if (eeprom4kPath != "") args << "-eep4k" << eeprom4kPath; if (eeprom16kPath != "") args << "-eep16k" << eeprom16kPath; if (sramPath != "") args << "-sram" << sramPath; if (flashPath != "") args << "-flash" << flashPath; } else { QString savesPath = SETTINGS.value("Saves/directory", "").toString(); if (savesPath != "") { QDir savesDir(savesPath); if (savesDir.exists()) { romFile.open(QIODevice::ReadOnly); QByteArray *romData = new QByteArray(romFile.readAll()); romFile.close(); QString romMD5 = QString(QCryptographicHash::hash(*romData, QCryptographicHash::Md5).toHex()); QString romBaseName = QFileInfo(romFile).completeBaseName(); QString eeprom4kFileName = romBaseName + "." + romMD5 + ".eep4k"; QString eeprom16kFileName = romBaseName + "." + romMD5 + ".eep16k"; QString sramFileName = romBaseName + "." + romMD5 + ".sram"; QString flashFileName = romBaseName + "." + romMD5 + ".flash"; QString eeprom4kPath = savesDir.absoluteFilePath(eeprom4kFileName); QString eeprom16kPath = savesDir.absoluteFilePath(eeprom16kFileName); QString sramPath = savesDir.absoluteFilePath(sramFileName); QString flashPath = savesDir.absoluteFilePath(flashFileName); // Check ROM catalog to determine save type QString catalogFile = SETTINGS.value("Paths/catalog", "").toString(); if (QFileInfo(catalogFile).exists()) { QSettings romCatalog(catalogFile, QSettings::IniFormat, parent); QString saveType = romCatalog.value(romMD5.toUpper()+"/SaveType","").toString(); if (saveType == "Eeprom 4KB") args << "-eep4k" << eeprom4kPath; else if (saveType == "Eeprom 16KB") args << "-eep16k" << eeprom16kPath; else if (saveType == "SRAM") args << "-sram" << sramPath; else if (saveType == "Flash RAM") args << "-flash" << flashPath; else if (saveType == "Controller Pack"); else args << "-eep4k" << eeprom4kPath << "-eep16k" << eeprom16kPath << "-sram" << sramPath << "-flash" << flashPath; } else { args << "-eep4k" << eeprom4kPath << "-eep16k" << eeprom16kPath << "-sram" << sramPath << "-flash" << flashPath; } delete romData; } } } for (int i = 1; i <= 4; i++) { QString ctrl = "Controller"+QString::number(i); if (SETTINGS.value(ctrl+"/enabled", "").toString() == "true") { args << "-controller"; QString options = "num="+QString::number(i); int accessory = SETTINGS.value(ctrl+"/accessory", 0).toInt(); QString memPak = SETTINGS.value(ctrl+"/mempak", "").toString(); QString tPakROM = SETTINGS.value(ctrl+"/tpakrom", "").toString(); QString tPakSave = SETTINGS.value(ctrl+"/tpaksave", "").toString(); if (accessory == 1) //Rumble Pak options += ",pak=rumble"; else if (accessory == 2 && memPak != "") //Controller Pak options += ",mempak="+memPak; else if (accessory == 3 && tPakROM != "" && tPakSave != "") //Transfer Pak options += ",tpak_rom="+tPakROM+",tpak_save="+tPakSave; args << options; } } if (SETTINGS.value("Emulation/multithread", "").toString() == "true") args << "-multithread"; if (SETTINGS.value("Emulation/noaudio", "").toString() == "true") args << "-noaudio"; if (SETTINGS.value("Emulation/novideo", "").toString() == "true") args << "-novideo"; if (ddIPLPath != "" && complete64DDPath != "" && ddMode) args << "-ddipl" << ddIPLPath << "-ddrom" << complete64DDPath; else if (completeRomPath == "") { QMessageBox::warning(parent, tr("Warning"), tr("No ROM selected or 64DD not enabled.")); if (zip || ddZip) cleanTemp(); return; } QString otherParameters = SETTINGS.value("Other/parameters", "").toString(); if (otherParameters != "") args.append(parseArgString(otherParameters)); args << pifPath; if (completeRomPath != "") args << completeRomPath; emulatorProc = new QProcess(this); connect(emulatorProc, SIGNAL(finished(int)), this, SLOT(emitFinished())); connect(emulatorProc, SIGNAL(finished(int)), this, SLOT(checkStatus(int))); if (zip || ddZip) connect(emulatorProc, SIGNAL(finished(int)), this, SLOT(cleanTemp())); if (SETTINGS.value("Other/consoleoutput", "").toString() == "true") emulatorProc->setProcessChannelMode(QProcess::ForwardedChannels); else { connect(emulatorProc, SIGNAL(readyReadStandardOutput()), this, SLOT(readOutput())); emulatorProc->setProcessChannelMode(QProcess::MergedChannels); } //clear log lastOutput = ""; emulatorProc->start(emulatorPath, args); //Add command to log QString executable = emulatorPath; if (executable.contains(" ")) executable = '"' + executable + '"'; QString argString; foreach(QString arg, args) { if (arg.contains(" ")) argString += " \"" + arg + "\""; else argString += " " + arg; } lastOutput.append(executable + argString + "\n\n"); updateStatus(tr("Emulation started"), 3000); emit started(); } void EmulatorHandler::stopEmulator() { emulatorProc->terminate(); } void EmulatorHandler::updateStatus(QString message, int timeout) { emit statusUpdate(message, timeout); } ./src/emulation/emulatorhandler.h0000664000175000017500000000465413623376647015460 0ustar dandan/*** * Copyright (c) 2013, Dan Hasting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the organization nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ***/ #ifndef EMULATORHANDLER_H #define EMULATORHANDLER_H #include #include class QProcess; class EmulatorHandler : public QObject { Q_OBJECT public: explicit EmulatorHandler(QWidget *parent = 0); void startEmulator(QDir romDir, QString romFileName, QString zipFileName = "", QDir ddDir = QDir(), QString ddFileName = "", QString ddZipName = ""); void stopEmulator(); QString lastOutput; signals: void finished(); void showLog(); void started(); void statusUpdate(QString message, int timeout); private: void updateStatus(QString message, int timeout = 0); QStringList parseArgString(QString argString); QProcess *emulatorProc; QWidget *parent; private slots: void checkStatus(int status); void cleanTemp(); void emitFinished(); void readOutput(); }; #endif // EMULATORHANDLER_H ./build-scripts/0000755000175000017500000000000013623376647012106 5ustar dandan./build-scripts/revision.sh0000775000175000017500000000037013623376647014305 0ustar dandan#!/bin/bash git show-ref --tags | grep "`git log -n 1 --pretty='%H'`" > /dev/null 2>&1 if [[ $? == 1 ]]; then REVISION=$(git log -n 1 --pretty='%h' 2> /dev/null) [[ $REVISION ]] && sed -i -e "s/.*/git:$REVISION/g" VERSION fi cat VERSION ./build-scripts/platform/0000755000175000017500000000000013623376647013732 5ustar dandan./build-scripts/platform/linux.sh0000775000175000017500000000142613623376647015435 0ustar dandan#!/bin/bash [[ -z $WORKING_DIR ]] && WORKING_DIR=$(pwd) [[ -z $VERSION ]] && VERSION=$(git log --oneline -n 1 | awk '{print $1}') case "$1" in 'setup_qt') sudo apt-get update -qq sudo apt-get -y install qt5-qmake qtbase5-dev libqt5sql5-sqlite zlib1g-dev ;; 'get_quazip') wget http://downloads.sourceforge.net/quazip/quazip-0.7.3.tar.gz tar -xvzf quazip-0.7.3.tar.gz > /dev/null mv quazip-0.7.3/quazip quazip5 ;; 'build') ./build-scripts/revision.sh qmake -qt=qt5 CONFIG+=linux_quazip_static make ;; 'package') mkdir -p "build/$TRAVIS_BRANCH" mv resources/README.txt . tar -cvzpf "build/$TRAVIS_BRANCH/cen64-qt_linux_$VERSION.tar.gz" cen64-qt README.txt ;; esac ./build-scripts/platform/windows.sh0000775000175000017500000000310013623376647015757 0ustar dandan#!/bin/bash [[ -z $WORKING_DIR ]] && WORKING_DIR=$(pwd) [[ -z $VERSION ]] && VERSION=$(git log --oneline -n 1 | awk '{print $1}') case "$1" in 'setup_qt') cd "$WORKING_DIR/../" if [[ $BUILD_MXE_QT ]]; then # Build Qt with mxe sudo apt-get update -qq sudo apt-get install \ autoconf automake autopoint bash bison bzip2 cmake flex \ gettext git g++ gperf intltool libffi-dev libtool \ libltdl-dev libssl-dev libxml-parser-perl make openssl \ p7zip-full patch perl pkg-config python ruby scons sed \ unzip wget xz-utils libtool-bin lzip libgdk-pixbuf2.0-dev git clone https://github.com/mxe/mxe.git cd mxe make qtbase else # Fetch pre-built mxe Qt wget https://www.dropbox.com/s/jr6l4lnixizqtln/travis-mxe-qt5.tar.gz tar -xvzf travis-mxe-qt5.tar.gz > /dev/null fi cd "$WORKING_DIR/cen64-qt" ;; 'get_quazip') wget http://downloads.sourceforge.net/quazip/quazip-0.7.3.tar.gz tar -xvzf quazip-0.7.3.tar.gz > /dev/null mv quazip-0.7.3/quazip quazip5 ;; 'build') export PATH="$PATH:$WORKING_DIR/../mxe/usr/bin" ./build-scripts/revision.sh i686-w64-mingw32.static-qmake-qt5 make ;; 'package') mkdir -p "build/$TRAVIS_BRANCH" mv release/cen64-qt.exe resources/README.txt . zip "build/$TRAVIS_BRANCH/cen64-qt_win_$VERSION.zip" cen64-qt.exe README.txt ;; esac ./build-scripts/platform/osx.sh0000775000175000017500000000326513623376647015112 0ustar dandan#!/bin/bash [[ -z $WORKING_DIR ]] && WORKING_DIR=$(pwd) [[ -z $VERSION ]] && VERSION=$(git log --oneline -n 1 | awk '{print $1}') case "$1" in 'setup_qt') mkdir "$WORKING_DIR/../osx" cd "$WORKING_DIR/../osx" if [[ $BUILD_OSX_QT ]]; then # Build Qt # Perform commands in /Users/travis/build/osx/ if building for travis git clone --branch v5.10.0 --depth 1 https://code.qt.io/qt/qtbase.git cd qtbase ./configure -release -static -sql-sqlite -opensource -confirm-license make sub-src else # Fetch pre-build Qt wget https://www.dropbox.com/s/mfw6qwfir62fy9p/macosx-qt5.tar.gz tar -xvzf macosx-qt5.tar.gz >& /dev/null fi cd "$WORKING_DIR/cen64-qt" ;; 'get_quazip') wget http://downloads.sourceforge.net/quazip/quazip-0.7.3.tar.gz tar -xvzf quazip-0.7.3.tar.gz >& /dev/null mv quazip-0.7.3/quazip quazip5 ;; 'build') ./build-scripts/revision.sh $WORKING_DIR/../osx/qtbase/bin/qmake -config release LIBS+="-dead_strip" make ;; 'package') mkdir -p "build/$TRAVIS_BRANCH" hdiutil create -megabytes 20 -fs HFS+ -volname CEN64-Qt "cen64-qt_osx_$VERSION" hdiutil attach "cen64-qt_osx_$VERSION.dmg" cp -r CEN64-Qt.app /Volumes/CEN64-Qt/CEN64-Qt.app cp resources/README.txt /Volumes/CEN64-Qt/README.txt hdiutil detach /Volumes/CEN64-Qt hdiutil convert -format UDZO -o "cen64-qt_osx_$VERSION.dmg" \ -ov "cen64-qt_osx_$VERSION.dmg" mv "cen64-qt_osx_$VERSION.dmg" "build/$TRAVIS_BRANCH/" ;; esac ./build-scripts/travis-ci.sh0000775000175000017500000000070413623376647014351 0ustar dandan#!/bin/bash export WORKING_DIR=$HOME/build/dh4 if [[ $1 == 'package' ]]; then VERSION=$(echo $TRAVIS_TAG | sed -e 's/v//g' -e 's/-alpha//g') [[ -z $VERSION ]] && VERSION='git-latest' export VERSION=$VERSION fi case "$TRAVIS_OS_NAME-$BUILD" in 'linux-windows') ./build-scripts/platform/windows.sh $1 ;; 'linux-native') ./build-scripts/platform/linux.sh $1 ;; 'osx-native') ./build-scripts/platform/osx.sh $1 ;; esac ./dist/0000755000175000017500000000000013623376647010265 5ustar dandan./dist/windows/0000755000175000017500000000000013623376647011757 5ustar dandan./dist/windows/icon.rc0000664000175000017500000000004713623376647013240 0ustar dandanIDI_ICON1 ICON DISCARDABLE "cen64.ico" ./dist/windows/cen64.ico0000664000175000017500000020407613623376647013405 0ustar dandan (( #.#.      2K]deeeeeeeeeeeeeeeeeeeeeeeeeee]E0J\deeeeeeeeeeeeeeeeeeeeeeeeeecV6-NU8AVH# )MMMkRRR___^^^]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\]]]]]]]]]]]]]]]]]]]]]]]]]]]ZZZ666\ 'CCCgTTT]]]^^^]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\]]]\\\PPP> +++5eee```QQQ%%%Ewww{```\\\???e  3ddd222===0005 /bbb===eeek ~~~RRRwX $$$222>(ttt=&FFF { UUU,,,){{{O * TTTVVVV$$$+yyyS lllR%%%qqq@KKKF...$$$|||%VVV$$$+yyyT &&&͓}zzzzzzzzzzzzzzzzzzzzzzzzzzvЛ}zzzzzzzzzzzzzzzzzzzzyyyyyzCVVV$$$+yyyT 5558 sss S ;;;4ooo$$$_ VVV$$$+yyyT SSS***mx111AAA} $E[`aaaaaaaaaaaaaaaaaaaaa_Q2VVV$$$+yyyT xxxA I$ttt]]][[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXVVVZZZ; WWW%%%+yyyT ((()))(((2222 ###555:  iaaa222+11-5 +3*|||S BBB<<<<<<<<^^^`""HHHKQ ///PPPOOOOOOVVV6DDDDDDCCCLLL>AAAAAAAAAAAA@@@@@@@@@@@@????????????>>>>>>>>>>>>=========<<<<<<<<<<<<;;;;;;<<<:::FFF^ {{{888888888SSS:rrrWWWxHHH  ...//////+++J cccddddddiiiM WWWWWWWWW[[[GGGVFwwwPPPQQQPPPPPPQQQOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKIII```)LLLLLLLLLPPPiiiSlllgggvNNN CCCCCCCCC???<ʯtttttttttsss{)˟kkkllllllkkk0 2نɔǕǖǖǖǖǖǖǖǖǖǖǖǖǖǖǖǖǖǖǖǖǖΗZ``````___]]]J###hhhqqq9&&&VVVUNNNINNN%%%3jjj"""vvv/QQQOWWWWWWWWWccc]]]+Siiiccceeedddooo777n2 @uuuppppppqqqpppEEEu7 rpppsssssssssvvv###r>0&#)@MMA*$&)*ChhhkkkkkkkkkifffgggPPPPPPPPPQQQlllYYYo_[ZZZZZZZZZZZZZZZZZZZZZZZZYS=dddzzz[[[\\\\\\]]]nnniiira\ZZZZZZZZZZZZZZZZZZZZZZZZYM./yyyggghhhhhhhhhtttVVVp`[ZZ]]ZZZ[byqqqooopppppplllB ј<<<=========<<>>@@@AAAAAAAAAAAAAAA@@@@@@AAAAAA333---DDDDDDDDDDDDGGGGGGGGGHHHHHHHHHDDD3 =LLLuuu> 2^^^ CCCW1! JOOO,,,------............/////////$$$!!!333333333333333444444444444555C:ddd ,/FFFjIII +%KYRwqtϽзЮЪwϦm͠e̜\ɕ\ɕ\ɖa˚kΠrΣ~ҫЮҵҾŵroqBQJ &$%~kS:cWWW !!!!!!CAAAfff'''J3LdsytqrvgSȎ[ڟxvԧdYyYvNjK{d?iUBkW,H;.H<1J>.K>CkXAkWSm[v`}cuϥvޭW֛Cҏ;ЌTڜZԛdĕy♯gkiw111uaaa333eee, $l%%%HI[s $"[]\cuވ߶q߫^ןIΏO͑|ٮӞ۾ԳЪرܿw٭]ՠ>΍vDЏ_sj$&%777{k$ 3X |!j|˱yݯ'{0Ƀ\ԝ좲L    bKϓ>А?ғ&~w߯x5  /^ۊڴ/ǁ2̇=َ̋  )PlsuuuuusiP( AiujEؘgإTݦJ؜3ˇLˎxoGtuuƧW՚7΋*ɂ޽0(všsǡtǣuɥwʧx̪yͫzͭy̬ůT\Yu-JyӬtɣpğϰ(/߷\cAӕ>Ίf~r)DNIkƨS֚7ώ;Бlئ֑U/ 7Ή ouz΄шԍ ֑"ٕ!ږqakhdY yw rBӒ.N]ڥkDԘooF֛Tݦ3̈ݾ"4Kϒqv|ˁφъ"Ր%ؔ(ۘ+ݝ-ߠ+xݺ9j̀~yIח@&f۩wKמ޶8Ds۬^e7Ѝ{˥] ΢qty~/Ӑc߮dgqO,12Z>RKmaprrrrrrrXZYЄ́|Kٛ~;=NڡmMٟ"z Hٞ[$}v$ lv{LONZ_^Gz~6|5|5tlo?37@ؿ׽ռպӷѵгԳш΄~#ˁzã r;dȽ?΍[C՗HϑÔ`Z߬ zh-  nxˁxU Ω8985752/,)%ݚ"ٔ֏!ӌχˁz ox LMnpzuѭ4͉FךHҕ۲]iT֝ii>ѓfѝ$(&V0  oy ̈́W{lUΫ<=;63/,ߟ(ܚ$ד"ԎЈ̂|t| Rsc2ʅ9ώKҖ޷zl^֟SܥYߪ7ύJˎpT:'  pz ͅat}4+ſ=?>BDAAv!ԏщ̓ʀ{LË/ʄ.ʄݲɤ %Ϲo۫4ύAԖ9͉SʐɷoonufYR pzˀto'$% ! WYՎщ΃M۟(X߲dףIГ=̋,D΍g٣ٸ pzˀχ!ӌ"֑$ږ'ޜ-48;<\AA=/AYԍщ̓N۟/ۯOxܬ oyφ Ҍ$֑'ٗ,ޝ/36:[) =;8B0_ӌЈ̂M۞/ nxˁxz|w#H861R/;6ltӊ·ˀLڝ/ lvˁVyjW;NF0>PJ*PJ+9KD) rH20,ߟ`a`ч΄Lٛ/ it~Uyi]9-ߠ*ܛג@BAЅ́|Jؙ/fqzxG+'''''% %ۘ(ږ$֒'֑́~yIח/דl msJԘfpmZ CLI#֑!ӎЉ$Њ{uGՕ/4UE$Bʉ kos8Ԑ^ڥ^٦a۩bܫdޮe߰gpohk4LfЈ΄ˀ|wrFӒ/ȡt kpuy~̂΅щ Ҍ"ԏ.ݙzlq9Ɓ.ю{ws mCя,ɗ?ʉpqty|ʀ̓υш)ג|, }vr n jDΌfkso!yʴׯ֯ׯױױױ׷Z άլ֫װ֚' /84?8?b000?p8?0?????pp xx<?./dist/macosx/0000755000175000017500000000000013623376647011557 5ustar dandan./dist/macosx/cen64.icns0000664000175000017500000006403013623376647013361 0ustar dandanicnshTOC 8is32s8mkil32l8mkit32t8mk@is32]8% FlISY?-flNS( *63W4/@G>.$ :4>E'2(E'$$   ]S&8  cXwSByk7.A>p>6Q[H.$;0@H'/(E(##    C+/'9!5)+=. 8 %*)<+*011 .$ 98;A&!6(E%   s8mkg#'J  zgҸ onWr3†b␄}Yr"B/xxÍSgt~<Խً?FfYh(OAL>  il32  ES+7# nOS;[I HvHUF]R ggRZ|XlXqH<v Y}<]u ]2hK5Nt;= D('*K&5\ 7[*J;?A>>4JS",/,Q@+)"%3)-*#( <E15IE=AFC@)'*K&5\ 9\(H9<><;1LT",/,Q@+)!&3*.+* <E*f<55YE?/).:L !<3("+0!,b,#$:,28B0JHA6:MH=I%$*!!)-C# !"#%B''90*.55 G''*K&5\ 5[,M>CCAA7HQ",/,Q@+)"%2(+('<E!"#&'(^  !"##"!Wj$())'&%$"!  !"%%&(%W2W/121122&"4784qǂ.=<;:9M{;?V~YT=x y{3 0{#y?>>D443;210/.--.,6h* Aq_E8ny}"## 3n.3|#$$*"! "\3 K 8pu&( |N&òie waKIHGFDHEfA~%2Df( ` - \Df yŔuſxDfY^:"iDf}x BDfvav6 C!h{Q&.$O.RkA &?; N ᷾ĩ -q$!ſ̀%˼ؽJ'Ӻ鳯_¿\ۺO '*>Ėoi aܾⷾăgy!ⶼ>MmnoYuĹ ҽÊƹAHՆռ ɹو~¾҇ھ=E ȹXΆb ĻJDžl տ} |پ۴└Kq̗uv|}wsȻ`*zxxz v2S'^ΦriWY99:;YYs}λX%N& RpQ v69 G]Ā ʺ\@ wE ;b K3>!"#&'(^  !"##"!Wj$())'&%$"!  !"%%&(%W2W/121122&"4784qǂ.=<;:9M{;?V~YT=x y{3 0{#y?>>D443;210/.--.,6h* Aq_E8ny}"## 3n.3|#$$*"! "\3 K 8pu&( |N&òie waKIHGFDHEfA~%2Df( ` - \Df yŔuſxDfY^:"iDf}x BDfvav6 C!h{Q&.$O.RkA &%va75_fepQ Z\eche(03KSE740=С\(2,B\ ~/33774>E =MLBG*: N0104533~|yvchq<$#mlCVV0S E/0/130.*'$"iA&,;w}NH} e+,+)+)'$"  e ]q(78pӾ8qIw! ;Hgiia'+2v{zwwvtsqplg1I48`LS*h$QRU^?"&'I/F;>Z=sx7C' ;!#!ey9Spd;v4uB*-Y * ^N  3KnX5xasF* w xc`abdefgfwB}fa]xtJQ20S w$&/TC:'i|{~{{ōdK05qrL*zxxz v2S'a~cTB>IeppcQGG=:03!"%"32BINPbcF4-CHQfT%N& RpQ v69 :dsldZRJ OX_kq_3 wE ;b K 3>!"#&'(^  !"##"!Wj$())'&%$"!  !"%%&(%W2W/121122&"4784qǂ.=<;:9M{;?V~YT=x y{3 0{#y?>>D443;210/.--.,6h* Aq_E8ny}"## 3n.3|#$$*"! "\3 K 8pu&( |N&òie waKIHGFDHEfA~%2Df( ` - \Df yŔuſxDfY^:"iDf}x BDfvav6 C!h{Q&.$O.RkA &2W1! JC3l3ܤX΃9 Αo_[ZZZZZZZZZZZZZZZZZZZZZZZZYS=֕ra\ZZZZZZZZZZZZZZZZZZZZZZZZYM./ѓp`[ZZ]]ZZZ[byB Sn2 @u7 rr>0&#)@MMA*$&)*Ci{)0 2ZJ9UI3/O+M VF)Sv <6>^ :x  J . 7 z.;D>`""KQ 2 : i+11-5 +3*S A I$υ; +T mx} $E[`aaaaaaaaaaaaaaaaaaaaa_Q2+T 8S 4_ +T ғ}zzzzzzzzzzzzzzzzzzzzzzzzzzvכ}zzzzzzzzzzzzzzzzzzzzyyyyyzC+T R@F%+T *V+S (=&{ )O 35 /k wX> )k\ 'gԌ> 5זE{e 2K]deeeeeeeeeeeeeeeeeeeeeeeeeee]E0J\deeeeeeeeeeeeeeeeeeeeeeeeeecV6-NU8AVH#      icnV C