fgo/fgo0000755000175000017500000000040412421500177013143 0ustar robertrobert00000000000000#!/usr/bin/env python """FGo! - a simple GUI launcher for FlightGear Flight Simulator.""" from sys import path from os.path import join from src import run SCRIPT_DIR = path[0] MAIN_DIR = 'src' WORKING_DIR = join(SCRIPT_DIR, MAIN_DIR) run(WORKING_DIR)fgo/share/fgo.desktop0000644000175000017500000000207212435102225015712 0ustar robertrobert00000000000000[Desktop Entry] Version=1.0 Type=Application Exec=fgo Icon=fgo StartupNotify=false Categories=Game;Simulation;Other; Name=FGo! GenericName=Front-end for FlightGear GenericName[de]=Front-End für FlightGear GenericName[es]=Lanzador para FlightGear GenericName[fr]=Front-end pour FlightGear GenericName[it]=Front-end per FlightGear GenericName[ja]=FlightGearのフロントエンド GenericName[nl]=Frontend voor FlightGear GenericName[pl]=Front-end dla FlightGear Comment=A simple and fast front-end for FlightGear Flight Simulator Comment[de]=Einfaches und schnelles Front-End für FlightGear Flight Simulator Comment[es]=Un simple y rápido lanzador para FlightGear Flight Simulator Comment[fr]=Front-end simple et rapide pour FlightGear Flight Simulator Comment[it]=Un front-end semplice e veloce per FlightGear Flight Simulator Comment[ja]=シンプルで高速なFlightGear フライトシミュレータのフロントエンド Comment[nl]=Een eenvoudige en snelle frontend voor FlightGear Flight Simulator Comment[pl]=Prosty i szybki front-end dla FlightGear Flight Simulator fgo/share/icons/256x256/fgo.png0000644000175000017500000014222012304661566017116 0ustar robertrobert00000000000000PNG  IHDR\rfsBIT|d pHYs7]7]F]tEXtSoftwarewww.inkscape.org< IDATxw$WuU'J+*,^6d0^06¯1mclL@!rvݝٙɩ{:wQ==y6Y}uVu=sRJֱu`K nAC̈́Zhni)B}C/,` , 9;419# q H,j8ǰ"(!FRRjjKUhh  7JcS3`#.fff`#=Бh)<ѕTujJެ;m}[5f@PS3fZ, 7z4eSLM0::A1XifpDFb~ȱcVX:V5!2!ԎW !~x)5&ul঻C]3`1hP4)-[Zil  6\K0dҳLNN14ܚ_|/y-fI11dr*dF̤NINμI % !sXwwzYGՈFa)˄7K)_WVջhmr֬RyP]Q^p{Cԅϯ9yҠKL36董`/S'p/~%Λ޶s ]'͒dMeH$L2>dbbCc gIH)E˘f$B0fqV>EEJy j*8QPh h 6}1W^3WE]e0߻|W ښ|o_ RCIX ΐdI̦I&LM29bll$ã &LOHgN0*01)帔r0}OGSP&)R\`L>I5IYn[}L_1EqSގ>o^D,>hx<^<?^Ϗ|x<>n/ۃyP57^ YR)0)SLNL22`hd)S@5t[~bѰSsqBpzLBp/t[6h hP\n>0{bR=0 m(Kw \ h8`mj^@Z׋O>#͐NeH&$SLN219Di&Ӥ3~iax<әsyEB EF~t߬b!CS\F>SKpiuKʐ;wzy[dC4doK)9xdCG/@x- ~>?^yhUugIiyBl6C&!ΒHI15bl,>6a|phhzKJBY(~eWbfldlP\Ј y浡or{mvŢyjqFGN<..u{<=BZ3(Bc$^??`y>Ϗŭy,g4gуOS8?Ig ޷7:XD"}_կBnc м =`0 틟g?[63>GF}LMx^2S4Se^_F B\>^6tD SыY~1>wbgu˅fMBvnj#mH 'xf_?)T-wRO0zsobn.裵FZNHR.'~uD k_[` xƙ}T4oAM810e4x|S޲^M=a " rmB}>^{6O.=B:94t0<:}Kpu/)J:qlz$$g33g[^;芴?ĹǘMU m[Vsup BJyߧO^O0GԡyP:TwCϓNLR'jgOrl M|7ߴ"'ӻ\sufgMϤ9chh 6ܚM턛מ`o).|c<pVRʋ-gYB"_oŽnG[=}!u#qm۾<.ܚLub*qFFϚs];6}SR~dhh+1 oB|×oM;9 W<ٷs *"3vuN@zł9fl<1'V%y@d>$+M_obZHs`y 3ܪP?A&7q\@|4AgZP͐鷝C#S15Zu|lſ}C*4A0^344RZ'D"> w͒;<뒇އz6ohg_]' =?I:14{sj:͝;ܡ/eo%3eUoH)#}gY"а(0;};xd{c4b$_--kAQix= n+JML\L96R^EQzB$2]D"[ۿ D+EcW7oF͒J_He |P/zwZ|_M=JhOyӘy@8Jaؤpc BzY (JJ)?MӜۙxB>=A*qb(o!LLYt/>k;ښb[,ioo)nFXфu>r-)!īt]OZȌ2[(cccY)}uuu5oBU*- =kLLhtr4#l+njg}nOZŅ.F65Bf64?>qCxy66{n@A`=xX,+[zr\XJ.MiG} K)?o[IjF0,9R~thh3|X1Յֲ92T=Q+\O{XA(Ǎ׫yiok-M烢hB1{(oJsGxpMzύٯB\388تDUU20cVӴUzn!DCX5BqIzD5qOy bt4ˁ86?3R~Lkk{\-{~p94xmwV>t"?})k#_Ia V=`v7AOĆM+< 6vOP,}q{l/}@ux g-zEM}|M\p,6БHg1͂׺aĆ?SְD;cvo n>P{NѢrE$we ;:4/w=9Vx Wj'T&u)p6/nuXncCWˢ&'B!3au(c!agY P"_I)oTE>җ!蝝<1exkv`UלmSL9oyQzyW'j6K{Ҝke"-Pu44_mOsp_u3f /H$|;g?ik_YPlvvrT~'GbziX9DϜ Eb!~:ڂxu'߹[S;z(L&*Cb7E5.ꦱn=;<WI]6ҝg-<:;;_+ `{Tb'|mLt ѕ- DjLjN\,SvWʵ|Vy1=:,~L==3UhoV֣ݡ-j9qhl& I&#!LwW#AL%H䛺ũZg!D"?!;K<7s%X gjxK9Ym}s̯nPzS;!r~05 OP\hbvmbENjx=YvٺA#_X$IwΚ(8L&>|>[[_/﹟½%@rV$9fA$܎ur߾W, Fq$OW y: _Euؒ޷ysIsH_yo46%)H)^TG]7>g}iqٜ乣9lU&Ҫ.E?gMb~ɡ'ک o/yRೝD",B)p0?y ykoe\)fԌo]Ŗy @T8*]]-?AO0w[ngڤ7I85;m"SL=|P1ÊHH_akƖ >jn"dScB6['cwvve}&?ԛٸ{"@2v IDATbto__?\j-|M<Ѫ/D$۟RJ!( f*;>jjĢE+p=Wv]\.O,>AX~15iIc}5ٴC99c&/Wl(%sDRgv(dlzf~?,xU$0XY]]]/4?Ҿ_wx|f9!BwNPKKNVr QHaZAJUA(UZszdv6aHrՠ0 ԡ o$L u6| un~%ݼE]*334RxO?":}[9Nm!_ng)},VkRXb7#?0N*/vn?d6gsr ^ 77ق-pߣC` 6ڛUz"*:4<5t3L3<2E{k0G}x;@z1Þ=rcLL-R>.x@@X|LUշH) GfJW֘ij"HM.]{67pWڌ z̿l+XŸ\E=!4RRl EשBnc 妜ar(N8_/ Yaϡro$LSESTF2>]no RQgZ!6DUy}r=m"l}aCL $;1O34d(z~go{^tKZGHM^Obh4n}^8w@58h4rb ; øt#D 8*Uo ,mP ( KI~ jCY CX*~Ea1BGJ6//(&3y`4}nE) ~~| F x䆧y0ñy A6搫/;Hg+x;l6 5nA(N)uRIc<}yꁫ.?fмeV3gFsvcukڀes zzz\LU(\>srN)iW.EG @"B8$ ѲWm_V7(9H`kd9!Aq)$1s᝷ vvT.ʦ =8uЙ>ˆoC+M7DRN1aO#;dTDT5:Z\x=*/<\5l<ԇ .X$@㗿>H ~Kxox\ɡ'M{8Y;bk> &FJ6U z5˵t-D׻oFBZj hJ"86Q}#XZBrjm5`CĿ{Fҧ zi{Մn}ִK_ uh4芆i*}MUiz|*QLfH>{^fcktY7E妭9d&mTj&e4Z׼-cb1t_KGD/׼!ˀsd]ו@ BU,r'R̀j`ZȒp+e!!4RTtj L:H "K$ңeȠhb+!(pK!7j #m\rQw?{]>m.B낵?M7\H b'=)&˚Hg[7;Pht4Gulڲp䲔S#+w~`ZJkߐ:@X, 0D]]ǣŊ_[ZKȢRÒC)`:q,SLQ49JP ^XdP18^v<:ȥbt Rs|F⩣FPT)᫷h' "SP 0>d|2gOt$0Vue<ӰM^"b?]7XJ) a(a\.W$h u)ud K,%-)T]Oª@y`Kl}})!F%듖Q/U=ԬJu-;B!-Bo`3zh"ɮ+U'R(J*6WUTcuцh 3j"z~2/@,=Cj#\xo=W/}tm1$Ks R"MHKurB}l޴lSdg^P{uB.I@RaVXd"Ʋ۷`HYq-!u$n;)_h 3ghEuQ>UGGVHB@rJ.t'ky>@-*]  r z6tބK0{]'6eV\zɅGO$!/wSZ0 Rx5 ˱`gM%h) ÒY]vgp:>rF;z?B&ޏ8&_L%tʊS&灇 왃0ϹR5(Z}c{m=nMp>~P (OTz,!*=n̹ '<:A3LwW P0$dLj`h .kKHBav+$|CV_iCVZ E#!D;Mf $m٥WQ@J=un ggS jXZQ&Yane-"v{ay2kL6WAԉ{Z[AW|$1X`STCS+61Ffh\?>3>`BWJOM,`n?0F55YS#]^G}C0>& X#PE !B*bv@ BJR7)r`d\8!H鰳LPEɁ8z{ۏPslP,koN  ?uxxO~,um@T8¥5&l)\:.oJY(K.A\46lG03Bu .oW3DD`oy;ޠ'⡣E8Lb>ГRW1)Jә"aŢ+ڣ$ UUB9;;kx<CaEr/P¥#k($:7S`K/LLbyK89{ߪEZD'$5!L`jIpJ-i(_w Wngqs羣9&u.&Ҧ2Hui8F!ϲ6eIHdzs8Ja%6i 33)#}h oGRlN{@K٪SCť&Bfvlj`h3 L$K/c:a+e_';;; ] UU\.j0(8ӫ'V/f McZ mRSq,湲TjG)~@8ɠ$h_؇aJº/E B+0+DсW]ޏQ,_+0kF%)݇$r7Eu|A͗yP7WA̹v.99TP!y :U6wyTyFM)uG$k>oiycN{m!2 rF0_(= k^WSe!6o`udA7֧a!+C1dXO4vE)qP3$v#7:3WU\)Cc%b!pUu|>y,RfY(m>gϽ'QQVu{mnvW_zF- Mp?Љ}4dA |1RJ 7z:TyGNોR}44.|.m, '!E>= abV K~LH@z^0 뺮iRe'̡6(!B70S^L°j7=U(rX$Q;l2zYüe8/Q5-&͝촴c WTOsZb;ܻf LLs3PS3A4<޺9@j}h߫H5(vvv>Y,=  ),@0xl7~K ^6at thi;;YATKe*Pob*s&7z<&8>PѽEjYu/\"{A}) X.I 0Dqo1RQ;D  <~4r3I$\C/kF? Z,!R>)xP(<966v:V?XJ0;)ax5m\Cbu3%^nH)-#H } RUsC;?8i/t/0ceF2ų>QUeﺮK0GwEt]vbRBauG h*h6={Zq6"gH{~SjWq"ak.Mt& NZ4Ύ(4& j^ =G1 M{o?}Ǟ>hjIZl6{䤭ž24$0J|ݭl[]fon;@s4r9D0 KKaJP(BmeSAub߾Њ+x.CqA)S%1>[8@' C*r\3R~8Y:OEJr !-ڄc0WjΨ' f9Fv`aO6uq"~g&B:dlcK_DimmGhkk#jBqeqO02`֯ۆa|rddښA͵EXI;EyTƷ?{e/@s`I:&V0w׷ҽyGSܚ`ǶoBUSg0>Nň| VihB1GV l|P(f]Z rpMZk# (J)Xf(n)&u]=GDRYIgbpf}R"yNOРT[5ms u>ֆUa022M8c &MY  [Ȯ];3{>3WRR~Wq[<?Jջ$N <]*}WN$pnǠa ÑW$lRIF vqX4[&E$f?ׄnyʪH@1gVĜcgr^E^ ^o{Ȯj `q;,-"izHc4j%lr4tu6ՌϷ2!/083&]\uմu04s_9>8'RKO` `K$ٹ x߷Ko=cKl^S 0;kȼΉi.fg)FsŇc9,Uì`5T|_[I'dI&C(9?pEཀb }I@R*LXP"ys$¼eMRʷ !zj݃Vit3;7#ܪ*ֺ^P*tfA?x)]ê0d -!Ԡq۷4r0PpE&tO|q6tKZu)4-p w~|V_H)3444KK&h4SJi3 l`Ǘyq_m^9Ws;_MV=Fȑp\U%w)"š)(5H`?ȡu̻\82*{hصo<ЗΡLMs|L\"07t8IX͉_ٵ.j] <`.\x43b5 GxUj w<cÊ}tztj"JrZ,vjA%UrnWs: kFȡ%)aw]J\BR @q`0} [82!Tߛ"Eå[PʞB!jRW]UZGݥY%u$F7^nMa4ֻiP4x.90ټݚ6 x=JŶ-pk=naM.Cqyxlܸ߳/b,Y7R 9OasphHa)4mFShEXXA<|(Q$0]iӥ׻]ZF*LlbN.kI%~C!uR7JII]4]s>ji˗pW $Goo|TWQSK%O ӊc+-/E66Aq Sq|`4U ETHp"_ ډjCQ$ wGvMvmK'GzVY-[<+1*k ~CJ}~i.DHMH qGy1;lʤ1woﭵV"S*޺&J\*5 4G0O-/ Ts:<`ƶ8ʗ5p4 hNF~Ҋ0::`DJy+0mtE J)@(Rr9+i!6` $v@W&E=bNTrUv(iEOsB$%"zt]z}iYJ+ ;7Ѱkf`2O(BϑY-rsY]Zڔ/ꝳizsۿoR8|BQOJ)BaE)e0()e^*E|>x^NzOO 89$:|'Q[3JځbvU麞UUU1 C8QAPE`E{B4Z%p)REQUU*|t:-&s8'Wh"X8TүjKǓu]uvU]uUUb(].TLJ7ĿݷhdIe[lc`x!16K `HdJV2K~NHȐI~ |,?„IXl X-۲dz]tuӺ뮮s;I#q "b7`yc6]L3(#Ea.h* 4 f$c l}U8ɯ@KZwHtV#?)EQ$IX۷J<HTAO,^keJ)B)+,:qyz.q XͭςHG{X.cp{9 H7=G> } BDBDJk-WB54M9<B;xNQ j`fW0`mͦ*t]nM\65o$(³D((9x. 3 hs7autIH JP{ ʮ;VԆ\ !^&$O ADB)EIb()qt:,;wݻ+ "SXC_p$Eؿޱc\t:VJqDQS@ qrq/`Y ?~l!Ұ7L9g- ˱_B~h&cy1%zt,P4M( ZsW}Z@ XN\w7gnvnKf(B!1LB`-X (xY\5l_ Կ8y`ڤV%7] j_*Ljf&! WBhc$1Zk6횢(x")} "YEiZ'RA~!3w'; ^rٙRZ, CaKԕhx[^\m0~"Aq-`!1ּe_6hβLk1FcH)7 :řq^ؘRBhBƘSBDTuRD}bW8i"z1ggMc<#Pr'ZXlLX3kyB~ozNe3c6Z 1Ĵ83w84,\8B뱬7 Kδc8ݽ{7ij$I<ϕRI)Byg G 1F9}h9}F|f:>Naˮj} !s\f!z*=nԖgCm} mG㎏xs5n dK˵ok--xAFpVDaI$I^ -ִsN "YNPaa_w^ںu+f@A6(q:VD| 83?W2l鹐cf>PMk*frԋˉ !W;Tu j~rϤB#Ws-ϝ|,އy1NQg(I=t;?N#IS#`,(L$U>VB"`p|ڢy":rS㧸+Uґ;j =9BAtP,85ksAe"DDB6346Oy:FZiwTm/ѹݟT6]ph^Q lxɭ>$!`\"%KN#Z oW@׭VK}D.x($xB8]V+J+.^ޑ!BhfyLQ߷3&8M6[ףuZ[7 ].BA@ԇ#|"6ޕڶW\t9h'e沔"qlGĉaǟYGf1{lfhOo+FmYn϶clԈ ,2 'xa.D@|[v'pՓ1{CIȍ !D O%PJ)1l K)y@g+Z3hW&IRPoI=N3ƀN RXKuT.x\jo="?<'Tdnv8~dL?B+>,xF.RLX,BZbppq J軐Lpj~ׇBFl/gI)1RƘBk]$IPij!R)?Gp:I#  Ȣl )*p@؂* x]>.6N.p%Jtߦ랉t}SNYlrc_3wQab !cѬ354!h2v؊%Aqt>7OƆ?N܎osO5 ȹ[qd@q\s[v_H z ]&2s7aˍk !uѭS$IRpauez~~^iʎnr2 0۷o''; H/r0N"*<7ڌs [N9s0vo:f~nX_?q|v4vr$r7s{/$\DDe>Awq[^nx6R毤g@؃ !ڔk0ݔ@;#2QTRH$tlyp w8`2fT$J)UM,MS]LfϞ=_?s 16IH)37,khQSkЀqu! q̀o@TW63lꩋ'8P!y kƘ !9Z+V2h8_u!˘)|n03ƄjVkY2J vJX'j^(op O,1Db޿k̯R?MӞRJcRJ1JTw:$N4͊cBϔEa?<ϗL*@ gu;PeP]*X@š} qU̬+-njQSu+&u3D`滸>o~y!Lڙ,3Yv@r?%Qp") 1/mPh1{RJ !t:I$1Y,ˌ\ỴSWkћ눽Ե+9<{=&rsAǾ 炙?+1CDigvi!!"l0W^BtXD>"]4"nCǰ-/l7.3[JMXg!Rg \PJE$+N l|ep;#ž} u"f[83_%9l8n )e3s{u~gGl=?t(}ۿ}p#oxÔճyDd') ~|izu{^j6j{ݻwį10N'\^]gobvLU\y-"ѧaa+r28)O ۳0{b<>5k%)eU۬=W|-B&'#(_3 Qs@|D.f^]~8}_`ǏހvT\#?ka'gRlTA6l!m^=>}{<Ȳc.%vH:90y\޶Q4X~S(~az3y.4L5e]7|M Y>q96(Xa\&"#Ā~e2VJh WfA)zk ={\ӯ؎?*S_Υ؛#0v9(r`0B"R8ч| ?0 `؁>^O1`?MEsX\U8>5u9=CzU e ?QX~ B<ύ#AI8DYX(G"MSqPJf/ѫ8~ˑkQVw`;L\>^ FjMaM1J-.ڂ'{-"Mo>QuAy|OxUi[,U"pr|kĆ&fj;GbW4Ƈ"5Ƅ8w)}/.Z1g8;aJm&c~ Q DlDQ?BdƘ,lWOz!, "^Ǯ/F S#]q?XS_Vdm|y\[yd|=% Ɔ=/~a @%O)eίݎ= yjoW|%Ei։͐‚p `zBtL;@o0WX]cD`cQM*X0B֚{$TU,F~gPvycO!$I։2%2u*hQFDu+yG1h8ݯpu;dn' ~Sxqam۶]wϟ{8!(MZnpFYM/4:=]?h+vpP>^#s8OKKK"@Di  [Zk!BmZQ ˲Eu~wHc2ٕBb ;pz `Z;+S>dBK 9ϟ۰§b%7p"@lHo? r%/yLSRp{Dv8Rgk κ!]WJmVpm\~;_5e[ai&9wBR!lH)3)eVENRCkEI)3tm7Q{~C0鑙f:8?N 2y҅CcUb'>Gs~q3xڀ4SFmx v. Fr^.,Vule/g6>A8 En aw_cJDeшjA _ Dk]]HEa?rDֈ(d$>K`ݑFHdۈ~~g~?&/;%u DƱgAV?k-5]th]]q竳Cr~,C3>|~<ap!~֔AGB7fzhn[_SD+ŝ)-P1FD[<;vpAGe'4Ck?AI"K_[26;qV r'9%(Xb.'G!f~pg{`B !~>@ 44%@1&d}\Ft׋).\1˱oT Ս((Pp/G&[/oT@PxO\"@9aDIR5hclRD+ IDATP`/ ^, m2$ K\\}?U$(lCutVDtIz+%"F1|',#s/#/BW/!}HJer-Q}=1POvnƼR3 =055dAgl (.8R熴 ۭQXqw\' =;J mG^vX:0) Q:Մ ֎b&{ 0=*SFD_FQΐ% EEY9~&P?z(5?]1q7-7Fs]>6t`ݿKDe׮]+>39܈Ol n`~G"8dztR[KXn t@,}nдC`m2a3:E_Hwԍ*Epډ$}oB8y#P6Bo.xÊ/RB J:;hc`nPC;V3*H;,D'u0s˲{^h4,޽{y3~.e ֠Ċ!-T(coFL0W7C PAk!H+%ݮy%KG29?q'`yIVA(1Ʉ-q̷ ! m(ICdjB*Bo~:p\Qȓt<SОElj[ X @`6"n.Yj P cDZ'0lwx4l@% Ptt"@U,u w L5YҲ~{$P^黹`Q!WqN6 w\ u78๢π%ܓ1FhZE۷o{mS!҂ vTSt?a H259`MD>$ȲlNdwD Yy]y))`1|) %&HXBR۫f7M2qI>|U'DLC5;}$*'lETXBop% HlK!/Nk0㼰`= nJYQT7 #'nN xEw ;8`Nn4 #Gv+ Kv+%p,qqb`NMȰJGdMqFӒ@LUHS+!bf{oM#Ev3>=R$IH:(df) %A8"1̪pF ,=$GX@~!D 1Ƥ#dT%I"h4 q\ dX>",wiA3'n,A;+ aB@lY|bmD-1-WL`#_DM?t"cWC%%VoD ΀ VL3af#`LrJ)eQ tWuB۷oZV <~u<KX8⠫xv\ D#Xqwe6r>/RڹXBr˄oɇ3 SAbNqf;0=aa9"2| pI5\O1߱EhE,'78Ae׏¸C!jgH&")HEQjB ֺ( cgBLrD`@| Cß| >^Dȹ^?pc3 0LB"9;eSr'!GGzW΀րAwz{ϥ1&OD9 dYn ~ q̜݀|86?D7׬!H8:D+,e;VB"I)T~_7M$t:C۷o{Xg&v4#|ĻkW`2bV_8udiA'< +sOCm*LC$Z. 0~ s0BYr[!=?Aey1B2'7pja)D M2߹v!@("Ry4M<υ1FZ-t: oߎdƲ0ZRе$u9niҋίD`%_~|hh98)쉅)χ(A(6@.kƘDcVc "RY^gå=[J):1(hc׿ XxBwS" fNDW"dWQ"cr݉[V>j߼%{W_%^4 [ːaEID&MS`q1ffccݻ7dp 5#0H@*8r].g*BW^&baa4MFǻIT:qsvYkn1cmf*ںߔ$ Rnvaїݮe/jf% FG|P.sj ؄6돿`[LV1T?I\)UG1n4f~~~m2yc k4M( 6xD0>03)t됪#8,Ix!tMmMdkG ko2};z;~zϞ=a]b w{[EuYKbv2]B_`f,,,tPj"*G%[lCg[аcǎ"c i'Qv0֣!"bsj9qeCrc\? Rִg` Jؚ-yHD1cLk *2&2KO,,,Z4M^cRfN$~?NwzR)%ln/qsߡ0#bzaߗoqqN0yVAGpuwv.4L쑫]82@8%ѫ!ht2PO6 %sN(rәct^ 0·˹a#@T$a$$TY 1fQ3RJt:*MS/ J7oy{1 afc2[O@z߽EƬXT^ 1\+X,׸îfNRbǎ&Xa+~qн_@н:= rew{'El=wtܱ}ﺿyBmݾ!n"ؽ{w{qƩ^Mj sCs;΁S(;Tu R"8>LS,,C>P& >ÝN$I,ӭVKG8phi:S0JyFSb*p2Bȼo=u=Ab 8dסioqz/ůG!Op&(4jѶ'|HhE>Ob5U\CuzPۇK"a z34Qڱ1H1FH)_:>cƘy?`wG%w@(ED,X,I)XC"?2 CuН<9B.ж2VrQg=)A5 ZdB4S$ Ay P^O !HJ "bLx6EQ`)!Ęcϑ-K-}i|BuDND"/\g'n bT!rҾ'1l#Q@|DG>sT-AIkM;w,cp2(Gk:wzP] =>΂wEmTDFK8n(EфM.^ޢD!gq3Ԗ$Ili%;ysvTT\YasC 3`q8d+*@3ƌGze=*͏7 `v}M$™-zk m'V촙,]fEL[TiAy9RR \EF G0۱` u89`J #$˲mYmkW*KӹQ㰋7EIQK{X; G| 7cZP9b0@ }(G{rk!r}3R-S(pqQƘ<ُe"XckaÆI!sT0XN>OvX\@XRz W'RMn6+oU.uSCܖ'a,A%ڧY6{~/ (f;gPGб K]mN$8==K25@xI+N$um`dJk}.`tN ?>dkL̃L2OE1s(!PU"d%Ir0<>-B>Y:],ݮv~VLpn>.s22f@ 0rmpvS,m^ֶ4X^׷}C1<@=|4[vB-']m&198 f;6}较سQ8xdhin]~k ,zcIv~lrSبvZ4 d0_,B.8\yF7mLO) B)yn`طX:BjA)n a^l# `:MӋh刁fR- DJA \qr GxXiFm8({E͔Nw$ɲktmˏlb9Towy#?6u۴i=x.SSQumwo[3_$c>be+bhOP&glِe&sou^g^$<;n^ ]df`嗰,7"g7MMM=/NQC=믹7"\v7f_~O?/|}BLOOLJ91??EnN{K)7gW]7пax?XRsD cg\[N0AJm$D\KG;bʾ֪׋ CkȍBX$PB00B~ LX.yΡd3w^hӦnZ4HZ ߀˘vuDT']ğ!D`s W^3t> T6Uqi6JBnTv~/,?E~3=!!Vlěnynzً B>>w~},˶MMM]733:Qz }@2̳g `{O^n Il^w^7`b'+׍_YXX Uh`u1hh&I2qх R}yrRpeד^Q+ %mDAD\: kEwQ 4;6|P0:s|Kpһ Y$!'Q:_ C1Ag;42o~)݊?{߯㭿}{gEtQz ։P&a}~?j=sjjX{W^|k199cTBF\ |Rx^9Kbk&" kbbgUaV~qݕh;Zelq33` K3 4.7g’c+L=kP RHp) I Ft|vf;tC wEy[k '{KGގ3s4MMM]89X.JqOW5QVuW=Y`+x5lو߁R!J[CZ 4afevp_ +8wk/!{P[^yF!gmoDԜxncg!qwXn_2==}Mzxi_wsBԵ q4M+ 3R_n=yB`;}<LYM*Btz L7  @,~:DnV ?:g1U}Y~oy-z pO_ʹV4)fXe$bo$'VmpK'''_PWoƻmXndi$x+kʹfyAѸ} IDAT6JO1);Wy{r?+D&0[씜6H( {]6ݵz` 6C!0Jb?J{9By΢xݏfz맧& L7!D:11|,"M8g||vqB~?pJƲs?\p~Ť";<0k߹`%pݻEm"SlW9=W+/vn@^|hçETႢ/-ìpu]U>N``7mZ?o^GvoGnyU\պnw:E pNx-,M[9?%X+$4>>t9B$ !RDD*9c?#ȫ$~ g"5 x j9ŏ"Z=x^^_["ؽ"V5 5q>s?rtpr8ۻߎm[:ǜvx/xMDay֍ү+izu7Dma[ùfNsJV`Y-Bs֭[ !DŐ({ťk? ]at:]SظaY~~|w[1}DJкQ]w煮zZc&b{p谜!}V1\KhB>c}՞k)=㓟gG o|րͪk,˶ !&1m]?Ua['''˿oz+^>)񱿿_7q߮较V7׾ [^;뢝aǎmسǦ^'"1>>~<ʜӅ#X{7"w8+k]p}IR l5^i`ϞGOpϷ_~?LJ>| hWW\͕x}(n4BI&$*wQ,  18>l߂k#-o~DzCQ(=?~_G~{esinEIXw#ejjj{[7~l䀦3sw1~g~}Y?Fz~ _{ewa"s;Fϓ.;Iu&n_^#=YP lM4w gB/\?~oی{`-\ vlxVsa1(eoR%+0x%s-$,"#L6F#ÛtHyx/6:ݪ#PSM& )6Ba\`z؁زytEU~K)aY];>66v)fKo^3fPy0`6Ak#oB63zj՜< ~-# = Hdw$I*xa!JNDE' r[.3o RYUf$y7o@ad3s!XiG uǎOm[Vh5l!v4=7ڑ?өoDn&D~ D^/m?#hlN*Fe\NY3FR"-c̘G$I #.M/Ί~9Ք[X?ltrX2KmFcּ7BQ_<!Ĕ^l6'I2 ܆/띕s&90F4mTf#܀ \?d) {3;H9Q2I uY-7p{#qCyk 9zǧ IZժMݻ[R926lÔ%Sm-F 26A+,T*QY ?v==oCx\A kە-^䘝ǻSkImS7LZb81p */l_C>FI^9'ܳ{dP+@dR+̰o@czy|J^9ulsǪިf ;Ê 9,%CWD$BTo!7bNџU巑:{ uh@6UxRZֽF%i:0!1l:( k|E]  yʲ ~4ƺ {swkl 2K {$I8q:뙸Eqv )⦑#?p/akLv>̵`'1yހLx"pϷpPSM4|#)Cb)kg4>wq𱰫?goHrqPLQ v]>eh%$LGBjUF5BnA5~An),d! " b_m 1ߏ8.`ݛJNl۶l˺Sf8V`C:鶴G}#=2MXFj@634 | {K`Ţ:czJ٢(Eq8J#gI/d@BDD57.GCzoU~yvXI% 9)TKPffXYITE;ϫ_"a oIg[O]Psu'l6; /SW!VV$r҆l+@Y c:/`TIRPJ!I0ZwREQ(y~Hk>˯G`3<4`<>>]nrWtM-AE/V~EakƊ@iNɮW\rC>P ǚMG+%I+V6uB?컉k3 P%@>4Mώ/zuWup7Uyxiջ\(p)BrW61??~ܱKyt&֋VU/E\<[>_ ^Bt,PO>@-J=:VWv03stƘm^a @=;? lU`7i:];r&ۏ~o- iexVxz` XيDۯ.1y#y\ʼ/h_~rC<7/@O|J;_./ ,x1h*hЧb ˰0w;w;KoUꮪsA=uNWWWo_~EHB~ʧ2 LLxρ ' w={c9wjv ծ~WvƌQ:94`܈H 5Bc^ z`sRCHsh2_nHi )͙LFr'*2`Vr#Lu<5kW}2444 \|>e?`ߘq۴i~caƛ ~dV/v  Wc\djAb,.]EI jBQZ?$jB~Xs`$3e0:|~P+LLJ#Pc>bb "7uڲQH/'GICqɛCS\&H/'۹SΫ+H+DA7o'xB3˽.oB?|]?<:Q9>EJS 6o9O?a";'M!TT/f-j"D> \*0򃐃QO˗-F6cݞ瑎[ #.Nm; œ9f3:rkCBOkē=ڌtСL1n%m\ jۀs,V: [Z@TG8o1tq  !߁`4YOt؈*d p䨞I+H&*FƶHstQ!پ]HѾ@ DkT4AĻ+? ?;"vs! @r ժud%> t]fe i XK΁ߒpO{{{;-C33!HlˀY$%X 'eh^<#B"^&G*t)I>ҏ#G㾚޾@$ӧ8):1$}^( Α8F7\ W%5\םFST1 {?*RV+jJ|U« i ϟc&g~hO2 02aɚ);Fƴ4$YtOtop{zQsl0ƘMmզmi)8A=aڽ{)O5n *#p<0\~MXR?й7 m&Ŷ-,^r. 4I\PkDNkV,Oa4EӯܦS.L6 ,TS7|ǤJh&>S <,׸W] mh.mN@ǂnTG ! 1VAnwwB{ep}0uTBnj]5> z4dqlRRő8Gf!v{{UW|۳hyyi84@ @\蘗zBu\WXzޙ(MvItɶ:G2 ByjOdUNgN٥sxHY,Y榐pĭR )րHԏ}3d$?!TRå9S_ @m[#iK]1گMB+0xlH;}qB$ۿ >~B끖8 _t>v' |N i ãxnT= X6@{oxXQ__²Y@?mL&ӣ)dɒfqBNeYo׎N(] {x0=z`L f5IћA/~;UB`f̎p PG%R@ 1& !, Y 2yU]ߎWj7{э1vFPDv/~dQJ?jYG%Qikkp@_,RJ$NF[oF%Zz*̄RʣW H"Il⁺"_* H1^ƿPTA7]pN dOr|s$fB}Z'dhlhoV%1y(1G }/)q >:dcieٸKb,D% z\kq@,$'DXq{g R`qX"v- C橶7Yx-pnl2$ίHq-+NwU q"]9Qqj}{XC'&* v6+\$V m\ҠAPJqgᑅ7Gn={7ci3TrݑBq+*k*Bzēimbӗ/04j ub6˂8At9Hh0S)zj,|(x&Y&פMEO*i$ /ꉥ$:(q$wҒ^ '˥AL~mH`Ve r5ƔT`# wSSӛc-JmQ K"aHo0ĦM[q7n<k8i) ;K$C>T^%QbQBjnk- 3@ 3ƴOP9.KZ[<$PP˂ɈW2rî'|#S_oKlIKpQ*j|O=5a>И.H',11@B EbQ*o}Eh[@\!* $zo_Ae jm{b|Pض 9'ʈBDԣ.yHCY,HF׉Qo0F@UQ3 QBeA$_*^d% 1hin$ZOEDE,)kZ C̟3cdWnER:QRt ơ&BrPܰUBӵ`>cѶ~qR?ÏD2\-"݅@.> rgY}awJ-bnp'g@PLRq xFMǜs}#xJ)qX )qq&5JM0,ZY `6=`뺁xR.64!2CplLg)ʚ!v۶ Ej{pա4~!K=8>~}<"w!M09G@#} `T 'scF3!H&}EIJ( 3D2ķOA )tRJϚ5Rx%͙37Y%wDSd7%4!AsP-W` LHt#D B+!tEs ptU5C`U4_ڄҮQ]&y"mL}RQ0WXRzcBe= 8NxEdD7>x"yT*RZf繮` >!hh֬Yn۶4b>#WM96.,_H !ƹeݢEXwJlVEuԅ  Ķh8wd|z+k.ຏ|XBek"FH# \9`ǁw2յ744?::oAH Em---gj{?M99] زmiesL ^*w<8R.\bϤ3B$L&xx;ҹ^'qu7T*GuFP PڶL&#ֺSsb_TUMґ18C; =CHC\~^~9аnخJBL6S(Ψ0j-ϱwD_InZ0Y G}`,2Y$r@]P ֬fHz1)w/<2^D#edqtgpm1, fÿ|@yUJ8ٳ2@y%˲"c ଳ}M1@ECr?]mx^?niiysss1sPJ(Yq_EY3OtKaGT5~I<x mldD S.*BWO?# 'ս'D'>l]`4tIpFuʀ(XXwl: g $ ތF<;t_T8$Y\N\u `ŊC{a^(!֭ CØl|tԐeQ~d0>x;?9'uX,|;lOyI79/oҶ(sQA ¥ٌf` ) X8Ջ(qv,kr*H*kZR(Ζ?O6/t_Y7Qǧ}<ijxgoA0c;g6Ν ;pqÜaJ݉)lMC q":tlug#B5 wcȧ;.ĽO? {alL"\k׮W\q9u9D\@\oݍO~*˄^yR]bmW==̀Wwq 8@Up1=!IHt db?XHfHPP*J[ =}nY|fM7$W EWWt4>?&!(UJ/ u ŀuՃb'H\bcHCi|*TT ` ya{'Yӈ*(LO4)%<߳g/>wA)>a0T.51tuusc)x pG---Xz=w=ϟ'!"Y6x/h9稐4X7?\Pf f~ Hv$XHE~@KKՙ4@W*m?xa}XdD)\3vLJ ۿwlެ}@?cryz񲙊\<4tAZmx z_$J%}2\q5π#dtclzIb 4hةX.T`@?nl_;y9Bě; ss|10Я!6D!rYԥ2}, yp5P1R?g,-_'24@T۽-ܸ8D5_7FF 0d[e aI9D)kQE;zApʬP8a0J=؆G_mhi':Mxϕ­~#c} @7u 8:22rz~|O: .6SU\8bvȯ08$ O۾x_ccc )Ujʕm O]?(f|c oO[uNk1+)Cx !pbJlv3'?{|S+G& GhQy C j%?۾-$ A @ |%ዟP_T^rK :ΓOn>{Q(. 1cdL9>ظ'%twk\pyTzvdd!]J)O?K9]iY̰uS5kcWSة>gYCW*#bBliiĎ;ܴ/C eL&^uܱc~zI?9<ǟ{eխ2'\Mcbq;Jdry C  >|1N9:fmϕm>F|ڏw}MMITP_+O}/~pرcVt0PVABp=!tZ)(g6%N~@BʿVEaLp;E~;ؽg_juÌn?7Ad>:i#0gϞ}eYQt^،wJnƅTFqUB"!8}[Ty@<#f-eYKc@KEIդCpj$dѓGHF3r?ۻiMj@8EfJP)DخaJؽ>tݟᓟ(7dDqE`J&l.ݺ68N3@# }ÈRJcid>;m{с;y Ӥq&CIqt`SWH! E<lusfBHDfjƂs~V%L?` Ìi4DƢ::#U%_ |r5 M\N(@l PFjhiiREq;܋k?\uoPCޞF T۶#>}{8]Ϲte(̓v!È:Aﵶ^dq\5h2XpP8_1lٶs/~_$5%?4<<8g9]0RīfQ!w&=E_)B gpRAG sǽz b<꾑q9Q@T}.-0ԍ0<s-lnn~g.KKqykq9kP,ǾqayRѧ#|!zn8c\nl eoH뢿܇bɱE베9#,k%s NZ@&5s~sooӹm{{NIBgA,d)K"1gAD`nCCEbB5 RyiXv%Zf5XDX@Xm[<k zq^ki9"~7b'7Xϗ⾖}H%A,b"dC>_xeYRhgcN44Q,P W.ݏ{a8|Ȅ=22d:@>>[#9N7oާ-jNL_)da2ضMA3$`1»dLJm]]]D?>D{&ښ~8(*:NqTGGGjՆ s4BË/_zjZ9{`lllK?ALv9`foqqT+Jqo[mF݃ JJ奱M1)⛒1syZJVՇHd,SRpJ*?Ԁ`?e0cLm+ !0T?;W'<kX}' `uQr `KXfh,kACC|L%cH5 0)!ѹvL |Bp[OOφ/KP'1!hP OJ|>f.[d5A]V$r2bԕ 5v5,>r$!hRJ#|>L&3߲5qH8{+U(m*TmʃɼQa퍍ٶ9ݤtBH@R1Vr>IDAT ) 䟉D#ݜҍmd}'⦧"LH0$R,kV>_D)m cȆy(&} \g]2Z)+U>BGϢRZ3JiR !#EC9 `A<;;JH">32?Iž,!| b߈`Vm7ضݚdڶai+6`#c쩾\+S!@`/!NkU 2U.mzL5 ic)EL)rWEr{mc2&!㐅K9r,d@JPB>Dfr $񓜾 ]ݑ4rJ1|xfbP |hmmmdVPJW@&~ʒ7@7r!>;o޼͛6mJu{&I''8H,'xUL aM^;] |9i"$@:p(7r,qH.,*R}"JQ=LU% +aܹl6L?tСcSqyaJ& \]HN($N(YQ Ùi=ߌ" 5b1ϯ9T B%f1T*A7ߛ|rJIjjSL k*Q;iTu_ I" {3!TfQiHYwѵaL{d\^ Sހqh&)6AasBIT|d pHYs^tEXtSoftwarewww.inkscape.org< IDATxwG}_=7Uڲ,Xb6cb}H$$@rCA jpdɒl#~ίoy_9M6<7|_Z|gVh%nKsӯߜN !Dww !^dZCAugӖY}}} |=< 5eG@Z냉DȱcNJMEJrT_:mK!KX2;3'ƺڽ躣'B u]{*`Xu8400pLk?O+ZWĜ'vik렩&* cjz O!;\u>p8>(,dhhS9Ee|js8}6Jy!utvtEsk;-$1558Gw`{oYBJhttty_ђXfMuwo16rh[ހM[[;m54L6sd3'894ѣ5S(qXq8ZJɹEtZv)囀6Y[v:7[f%)h좽5:DX֊R1,caz(SZPmbX1 !&Jʕ+'qoVXqR&5sm=6}t[oFPJkD6;8sMM-B"ƒ6= w\T K $f60923롖~Y`UjB^1o.jjoob/ZTqբզ&,~uyԷn&h^)̌2:2cG9px'Nrn}mElٹ9&LL1:extffs~Tu.$ՀY4eMMqg 󩧧g&K+:bewvKض%$ih݈Ck#ޒUòRID'DzcH[ u]B4s3y&'3LLechdYff5a1.3 Lj'֓J+VL=yPs!m5RkɄZ֮%7s+~NBfI7BJ"#ȣ;ٹآm9]驥ڊH$SI4tT2E.M*"L'pNu/,N +VSJ] \3\p%+3@8fb&uj~?oY[_Gybo?Pk<[BO$I$$IR4P2"H' xq;ıZ+|D-Q* ds9fgLd261,O09+!Oxm!DOOOM/nrcd.Q N=D3d DҊ/|~;x%ݝ<0n:øh:UW]c|;?ÑˎSm;Cp$$IyRiRdT2UΞ'``0ÑsV{7(Fκlw|\{u8*zU"p+زvm>-a_Kze'<6w;IcZW =umoyvNf3z>=o ^sǎ=V%o&:Ãz8|+G,Z }|O}ZZ7΅&7v308w|nMvHexh8%G&Y_<mK7p.Xql]!˵J)[V!DH)644bC $~Կ_yo\g #_4ϻK.HR;ih>9FX-4?zٜWeۖS{xhhAk{6\;::p[_5oz˰,ɉQ_bagseq4^\H笠8+ mc!M+WBu%hrRYgȟ|IBUJHUws-Mu[w_ރ멼=, _ƫ{L߿D=^w:6i@}J}kގZ}`FlI+W\sS9__qϵ0/w$_ln#3w=!nL@ n'3Gk_UK\ku;nC#?~8Ϗ38ꗵɩ ;?㻎061G]6͌>AڙSy+H&xww*֧C߷Kv['3u$wܼh>{.hl8-HA-W)_KJs1Irm)ypzZ_t?{|$O}J~ú1qA''898qJq.T̏Q*Lpֵs{[hOO[=00pN)_Yْ_%n=`c('=0굠J4ED㰣EɚS\tJ򧲴wo? ~tl_z m7ncnΧyzmJS2Pt9phJZ+Ӈ$xӍ+yzӇ{a X`gztZ3w?RzLqɓ''|KnR*sp &= !,T]18j~D(2~jqpե|[\;ۍdv4{θ!՝gR7wqMWT@!yA )LE(yRTXۧŽtM:I&r:T]z;lfh'yNd>0lqU1͛$qVh'zPtjD<~OI7k:e͙fp=Ż> 㓅CCC۵g@rl*4(Bqֵ:0j ;6ˑ;4YfD* )4]'b|Hl[IanQf;H8 u]6W%H{wS,zϸ8u-Iub!ɓsdz zc!3[G|l)6=fYVR1!ko~ ֢FWn3uJ;XJe+ <Ӻ"Fo,&Z\  .\C2bb֥ia ]U87env%:P$wv#m- S%zDD,L}8_\VW[aj1dw}A\O}yppuҾzbݺubrrZ;XZEK@T,[agV2R"`X2`t2hS(ljv\/<<kkƋibK~~__[O}J2f]"R+Yy"-N35 Է+'n~'ʝTNJgը**ZqRhJGN|P!>PbuAPڔAPGeh6U#XQu5ǪnIfvXoehkش6ʞ thEk,D֚qa[7a9erbftʟ4C'OQCfBh)Zޫ@Ҡ5j V)u蘲SBJt5X{8|tR ek:!Pe$6+>r=62lVwУSE7l\`}grӍED^ sԷmZ0a>͎+UQ|9snŨ ~֦\JJtldFѥ4(U3SBhG#XөG6!@jDVMrSs}f`#LU1)HY4GzRiB/XѿsY5|X2.Y:N4ɡI Ug!" Cy=Ҋ-(9Nr|0wexDZm;y'|4n[*y(G<Zic(EX]-`OR89^#(ѩq+GPٚœhZiV>:$+t7N–:?@kxTze"HږqnJST46XEhq5*Ona5s.\ЍG9ǔh@ qJıSXr=b|@C 0 UVM/fҡX*[M^UQ-Qh]`9{`e֦d2vJ \?/r;Ƕ 65:hkmX=|n:ilj#]ߌ$zJъ/Zǁǻv'#) Db'1րfAJ7k.@> \5FQ Z+t:<E"U{ h!VJ%Py%R*;!ZAuy^ PՎRRH5v:Av\IjU'šIve3Ჭt٬mbͪ%E\C LxD>z{hii%sSv;ax<3=DtP Om/PpZCoUńV~t? [Xʠ;f =_(4fRJ骎. ߃ H۶}ֺ3!!kֽBD̦~J%a[U.Yy)&;NfO?#hhb[#Ҭ[ꕭq&3ZN;qqbNW$w5-ԟ )POo[[iɥãQ(/oDA r#"~Qo2;RE>T#AWFUBk/BZooͤ7 cNf*woTy0AZiLuPEBeY @viR֯!54>o}ՈEp5s)6=kC tpt֖[Z]b 붐y$ JB{±m;vRuvvUm^R Z9a10T}Uq81!DBkI)ZFfR\#ۤczEV*48`Ty::*7n]v2EKֿ+k`."+Hb ,۶e{{0DA6ˢ+rRJT*yDBA Nz@m[Ik}-:^r)gT8/Ӹk#g5ﶦĻjATѽȲ7kD BH!btvvH> N B@fjjR)Ɓ:!dȦ)Xk]X\BhP, 7tr#_70&%xMBJ`ASpY U"|(ttTwvvbY.!@ҲR*N g !nZ_%,INj/!'t:olT]=ו^ZFnŗ0hnL =`Xy^)HB ϟ",8iSSS @^kB D\B7)R;ϭeE"ϗ#S-ksguB 62SBJyϕRRvv]W<:Y>|pK)KAy K!B8s$BIuNpFF-L,l.-8F:±5דm%׊ӐrQ!h~hAW [a[p!CnF)"+sY_iB{Rj cITRJVlqO1.9^DzS#B|r!T: + c6"7m]K0qYC ~}1 tZ |9@ooGzzzRJR) #@úء4:!|1@U,>LH+CL=z~xLDy)!R m;Pȸ3ۄR<~Țu2P&Qr a%X~=?J(z,d2 r>4fs 2Yu9!(x,.;4~L&fEPB0%6T"m orV=FWG.&QD"-r%˲<)`FkegZ\gSO'X[/GA9"ʸJChwz[C0~׷%€USpqkhغf t9L]l.73t&OP0@ZvtJqZ!eNoL!l ,@*Pȍ*>󢵋Bh4\842+"|@>sO RP:@"1+)RJO)I)=mq)8¢q!@PtrGIº(cL?H1\71FfqRPyEWn!BVNGӾca&T~(wJfŔ tvMFd Be[Y p$tx4wAWM!tG„իeC?cHP +RNkg|ߟZ)J 2=tqo,?goh4ҲPBhbJHdX,f+p._+XUILzh? e)Ma#R([Օ?aIB =DQL1/]2wYDL@% gY,0 `ڲ  .f/H_~:@Ս|GBvU=:dҶLg'd"f"b2n %h%3Ja4ED9/ m n 3Kaa@ V=#5uz־m۾:RF] Ų Z72إDX1j&̫tzlZVywN撴-%H,EhRfR~҅6um>.sIH#!Gv'۞===jh@v_Y,reNaUFCP eKO92:eDit!FǴօPX#))vR6+X, RʄZٶ-K$~8D`"9S >uCgOWA)BJh|cV~LU (Q[ұЎmO J#3YZ>F[~,LVicR(gQJ< K XB'LMY} eł ( !Ji?ܬg .`*nkz?*+wnl)g _뎫M"0"!0sRhD mQӞ)z: Li !R*&tR1a9!6 *Ti]Y跴ɔJ)3ݮ`v!RE)eqvvyz>'8wPeC Gr[kNsZՊ|U!G)U HR(y~M9vQ 0N#[G(F˲ lT*9m\MZA[K)}i}!#t0,~Ƈ`}y/;\Xk5,M8PyRd2R7 g88{%0楪_p %KP2q yRԗ13h%)KQ^T$OիW uѣRZ_c动bԎp+RAelmǠF/zT*edA)n=sJ}NJ٠NZZ;!gNeY۶Kd%˕}T X :A#ȗp39ܙtw.+Y.'+yވRj: RLcQdU•ղjPA@(Lw6g YLI'[qX8N}R&R )eT*yBobbŌȽ RaY{YP lH&MutqU2S#K,E)Q~(C%8X3`Ƹ>900P3۠g (Rpr=W/@ctTT* Gp,\VX½9B$?m ~֚$9 3P55踍cw4SΠ=umaGEMy H$%II91$2f| -N9 L8\(iopTWwb] L8X82#ҧnPjdF% Ir'==H04(MGsLLIx_}}B᡹a&5YQ1yT☯pߥ 1ZmVWWwT+ ȤNp򱈖QU zت5kh~(F j?ZQ>@4A)ΐ=1ܞ~CDb1Jnz[ƥSǧ={cdt"]WW"nnnN)!DIَP0~N`˲΋㈌5[[6׵LZk<0x~b Ƕ \.%P\8Vs{\1áA +ClrPPA]~QkD" *UJ)j?Upgr&;O뉧hll|mۉ٨ FqVi-֯}W]}Z-,iRQjItqB()X$|fԔJs\ JrﯶS (3YG{cHטL{x~0IJk~fR|L}}uZl6; b)J`[2|eccRJg>u$OeR5bPTb4ڶ nE;0h+/h{^F&s/R샘`ŸqW.xuusn2o5441>>~ cz@eY.@DJic7JQq.1}(0 [# ݂o4-c&j[.źu,sCq4nYW>XG7Fccm7%0rHTě^ >F:~@gG+4755ݎ:@mu8d29RE`d%*i]N}T,dhӂ%7%G;te2R臥 ָqBiYwF;.Ur~^{yV替UDELywvvvZ9JcȬşɃ|?ⱝ¶-.ں;n{/z^_̗[>۶W#/jiiSJ\qVڗ/[O&ӟO~r#M!`>.ٶ7tt,6/.E6O:qVwң`箲?!tg%bH?D.b:Rǧ#~Mk(>s_zE#f ;c>:;Zp%hae ollqֶ&O53Kh=ɑ'ٱk??#Xz})۶ng$m8N 2>MQmmG)}5a>_m/| 5~BjFnE׆7Z| lP_Yq^`eYK[|EsSÒ׎Off3 GlE\C݀pm%Y5x(+͢*ywDBTo}H 3xV'O9BH!ր,\\!;y',$lVm nΥ\us9To;N} 9r {5-|ec)XR@Pt>y|Nbbf1:Հ~},C]s)k-YSO _YW#"f~Č{D"C\}h(u]{ۈ6blI|h ^?=CGN-ցylH:>OJimp[.XOVc`piaM7ɮb ium]y}O^G푶m7\Mq&rJT/0՜-Ł9P8U^ 5~:q>!۟wa.FccS AZk[KG*h;D!+K#"p0~ ¶m&Ҹ'1s. !DOO;+Δñ )%uu):mLLΠ&t֣ŊI!ğo`g阁}cZ.Yw p$"{\eCUC/;'/ː]n,A<ۧH^ݏ`,2y$NX2pM[E(r;fȋORo7In:eJ MCȋ.H2QR}oq3B#5wh"F[7Lʹ}?rLeDx'-Rx<4\ Xa6Sf XMҊTͦ4?`UyZWq jyM¶h ij鸂#mu{|4ؖ@x-l jYzBToR"}&.å^yjǀ\.[v}O9TAΙ)o^Yծ6嘞3K.ׯF +`QX,Jv/UQ__ߛ i1 mYViK?9@YQ%q466Ԏx!F\Aɧnok0c'p](fԹJR^}0u]h A/5o,h͠ ءnݪCm 266Rs]!0#q^9G)'0`E_YJ3OF޲e"#zHRVFcceU)nգS̨ FFF<d?j xLR ۶B~{fݲ +W{ H_UwAr}}vsw.0/*|(N*q*;lΤ"A 05>`/ig/|==+^T_w2|J:W pt С0Z5܉S-frHmYH7lIDATg&--CIUdMÇtFM6zLܢ;< ˲p $f/j)ffQIC@EKnZQ6ߓ)S"m2㳧bm U}$tx,:P.ҩbDrm sç~h,N[3@U϶=ȍj#---*뽁t˘ؚ'ʫƺߩe:::ci%eD7@_A`u]iZ[[{=E R!$s^# j4.w`:"hO"!I|!4f6@ Ke5=dUjtP)Ν]ѳ «*IAp6UZ$UPq{pSSAҹ 8XԐB}.]ֽ>bD9p ]}?0V,]N5ne 6J A-⎔H9g|b$U߲U+8tRŢUt j9iҦi^B/d2fٓt]?!%8WG[#݇q4'Ht!_>C*H1e? ⃖T5q靈c!Ol!~ꓜvbnێ>la>n6CkAy 4ŋ;7oNeߺU+x7J@X,%$d5P/rHJs]'Nᕷh5@|릯4:樋{kǴi*MMM(mkn8N @={`*_zsm=Ba,4 g}&^M+ CCøG@F0|⺞R#*Gx^UU Pg{J Zl(8cضtWČ <}2C~q#ͭD1 bJfϞ5*Bm|?Iww/YyɅsXG˼}'ߺ{H)̘hF.Pѕ񱻻__JմAKذi9xoF+e-j  yiν37K:XtQ~*L 䡇;.T#ӶivS\}՗% Cc՗.CFd :#aĩWߌeٔe2ƲQR2yԎh豔^Ab6 X&;$ KBamTFb=xR 0}:}GJ%90>>$[AJo:wq'R*l?nVy)HR]z=jkZGPPQmooѿΓx^ =W`pp|xq_?>>8Ւy޶f̘q,X/ZquxʳJ!dEw{=>X||hh论i_KNFK 8xɠ ] Svx3 *_{{{o rYrj]cccORo7p-_WTbc$O~/yb*t\.;22y$dјo$].2&XT}[`Y֞ 4>>Mn3 c),!$%yc>@Dp;h־+`,&/bTh`H)顡o}WOc>pƌ<˖vFŇH92]).n} >J0 xg5FKK秽XB{] LЌծi3f\dR']tgoTzu-oJƜHSˠ<{!|L&sz \*J EIhTFԲ|>|>ֱ-B3tUQ]UyagF j8\-*:b <[UĦI&H*s5 4qr*0 caM7hRzaM[JYt۲AtC(RޙTjz&`ЪiZ{6]i05M+躞 KAXA|,k;P3/#R6ڳ_fGI@ .T@^QgFiQk0$x xxaCWW3ILb סP#jP& 8ʞgQڙ6^>CRqȠ#P9REp D׀"pf >8{삮 a f C`냾aŎ7L񏕙ITe(&Rv ?GM޷4h?CCLD>a$5L5*)"F9Nݕ&K8;fq 8Dh>%5HXx+#NVvU}8T~pH&qiǝIx Purg?soWIENDB`fgo/share/icons/48x48/fgo.png0000644000175000017500000001217212304661453016751 0ustar robertrobert00000000000000PNG  IHDR00WsBIT|d pHYs a aJ%tEXtSoftwarewww.inkscape.org<IDAThZypcf43FuKl|mHLB8*po Gl6 $%B8(B GT-  ' K|f4f41,CZ{{Lb"2H# D8EC>OݼкR `F1?Iݝ7\ZscDsK "L#w`xtW_wZn{F8hoo(.mi0;h@"hjnFkk"uP \(-xHy}ѡ7N6{Qub3US}5a9]U"\E$/D \B=[)U&fv=T*y4)b4wSB~$Kg&DzŃFuG\I& `*-YL$'h?W^$!-05(~*mh!%4-@uu5$ 5f f[DӌVDMl$4 I[@sss9wſ|\ oGNJxd38ii77J;ؼeXpWH 3;7 @KK&RbW,l[GD4 lWka,ag-b"Jx"_ZixiS184JJT,\A4JV/?[%Y3mG6xR2v57"UOiY:1yp```466gu7=v7]ZEht뻄4NX濅y-"+[8xjf6cl:o15}/ y,~aePΟDD]R `[eX9Ӆ"{8d? :<RAcVCwtY/mE;ۯw\}D$ۿ.K "F۰/f׻4|F}x_翞DD )AD8#Do0lhaL!4%c?FOW5,C# JG3ڶ?k#=^㫷VСC hiiuս-;A@2 H4C@H H](sSO^(QTa__'Fm؇Ztu6gӘ0Bu`j[:ʛ7w%)& f"v(H D I(e*0Ȑ CFlȾ   ?ނ7㙝ʭ~aw|ɂ* [#:Yb#P݁`,i!;~r_u4'Mս=\rEbz`OK!8Ifb Ђ H8LPDX ]"geyGAr67SƆ\3 ByYK~93nGiDD 6b@KE|_ ϼ[pV̆֌J6c"\Aqa0HCZVJ"`#;8bfg l:9r`0ŀf a? ],OJ $r_B644ĉ0 @ }ӣA DT* J)}3JKr?>~D3I|+q`3?:o{ ̬m.pk>z%!,$ i#D4$Z#?w'q[ sxɄ:;^[S}`4=7w|㓵@<[Li EQ u.ePX!ʮ-‹BсDXy fG>鳰lqdoo*./%<f8z8뼯+eJ!lVzR ThtR w^o )W_AWpnG W DB,ĭغm@_9ɴR RV|s#]뾱cmmpۏr5Hb&oU_?O5ξ8+NI̚.llcCgvR{3_r|[0XV˲K"Փ߰SH]vh@`lByU\pB>_b<߽+{~fG>t.#߮C+ؼe'~X]r7ϟ;sI׽E;7=%zf ,S6, L'GasOE<A'?#P۶yZwl,;8n -+}|/+N2e DjA#)ضUyb̆GR8KN6?qw$3X\EN@D~w9]4!7#]mQzPF9-yH@/FZ9aZJI@J #4Tڽr9 644ֶj hnne?ysSϣyX +z<Cq Qә)\O~Bls!d2yXllcJVYfUƣgSP"M7abg? !*gIݝ a>FzuNNnAbH)PvdsT0MvݛP(<8fmg[8_tH 8DdCU"*;`2H@6[@XҡP G 1\fLt5 _]T6{ hĈ,DۥֺQ|׋oqpٜFYť x_RC֛W\ϐŽ[pMP6@p]O($㉟mH `=@uq#umm- qwr48sU &].X$!qYg#ygWoZ[0pw"܎N˗.7M ;O70WFO #g} ,Z8zHGXERzַWU---0 ~Aʳ+㆛~ț6oqSNY!`adb;&>LX&sxZ5AP&?|x̘144{|z{ww}}{‹_Rc͚? >X¶tŲk0kl۾lХ2\-@%XƇXᎁop2F28X31̌D"47 e˲SrT*} _P( P6c)GG䃎< 7kfP(t   vK4& +<ӓ;+P6&LP/6 |@Z0E8R<? hP-lTJ f"4~m;HZL Eh>׬SS4}AD}N@ӆ^Fߋ&IܘVIENDB`fgo/share/icons/16x16/fgo.png0000644000175000017500000000205412304661367016741 0ustar robertrobert00000000000000PNG  IHDRasBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDAT8ov8M$-vϴU-j*-Zi6v` q.? 8 KŘx! 6V7,qIi6$~paIX뵗 aeY?~=dFO5r /ti[qn&UhrQY[Y͖҆aȲ,eY >j'9;+ha&LV] V7v?=~ hBoBmؠli`+{XP6Gg'.~ۥò}  .N9`^|t 4[(9bc#珪hLL51P*hIJZ#]Yk椽*JT#W^.x$'.1b @ǟ_ hW(2c x@vּVژS6; (aʩPJ{Ymgw0P{O5JH7n`s1 l$W6KM,(-IX'UtSJFq8תebQ,=hҚ^!0 ILEʋR%esJrPqW4᯿.|tp cK image/svg+xml fgo/share/icons/32x32/fgo.png0000644000175000017500000000563712304661434016742 0ustar robertrobert00000000000000PNG  IHDR szzsBIT|d pHYsu85tEXtSoftwarewww.inkscape.org< IDATXWkp\ř=}fI#idKF- yB Aֻ](07lQIBmj[ExTC;ƆZlc$d,{$$KH3hw,ngsouW|5#"mM[NoJ֠:744ph[+TPɒB\豞/-8op,uuu2FǝM NS՚\e+4F>^G 1.ΎlM3c3g]0MgMHHLLL"#;h޲ֿmgKAѼŗ`mWSS)x$,)Җd"1G}}شO04]Z/,{c)\ E!Db`߳%0ևNgDv^vn[;sS,㔇ދr2h 30JՀj|dhze4eDߨ _Pg-C}i-=vq#!=[D̽]'{YkyP?>}_Wcuqg 6d!;u8sZPC_vW,GnwsLwlZ˅1M^^knne`ͶMKwj `c~|kC.s912Q)`98W{5?y*Hl @$5[w1*""$lʑtP} )7^w8z"[N t-l[Rv. rP˘밊iu#'?J`m7oz۷Y}43clv !V.Osƻ@gFJjNG"Ed4lr#w\񞞳?#"R:n^:k+A,)dFz*X%'cq`{zE^9d&8Ҁ17aVw6Df2ugc9P<˥|xݺ\85.ULoqo䀮M̓p;)uBQ "1&)^HNҧgIY(NsE(L|;AALq- [^vu!WS(HL-Neli{s.J[=v:"@f&woeӃe˛wjhŧ^ndqڧUo[[ify 4 Hd$a`-$EDUe̲|!<#zзA{Wx;z-tR.Ul2XLgCd!a f*X3)e1&8)s[\v:nlrS\W$0ߐHۆLX"@)P|.j j#QӚ-yh1sΉ4ӹpOd̜iZ(g K&^8̏Ndۂ$%XסmnO$G!ö_'l6)!={4!%'cI0FLp '0&g8"IX߹O}wVQw_/~G@se!_0s3owt')Y4y+Y4ob3O<N=o]_Y4kYfų?ɓ⁝Or:vPeK0(""ˆL` H[MTa:[ylSO;+0ޙ7tJ}yKS P{Fykc娮~Y0TOal7Yϼn}w՝=ORx,wWVpݓSX<䄬Pgf2g WT7" yeuS."LEG4_L&L"1}RI]Zs&hyyG+#r_+aG?wBa " +( \Drmjo^h,<=*r۳y'T0U iN kuJTzqύoTx;О֤;M'g d [vR^l=uXF h^Fu> si^Pdmm;'mڤje ͬe?Q~SECV7o䝝;vw\$Jꮮu{;8l-#c`}"yMv3Bv>[6}g[kKghwSSg 8K *éTjzll@29cM\W-}]^U0 v,BKƋ{zzP81jA죱 7#Ƙ@ "-bDT`yTcw ϯP! vkiDIENDB`fgo/share/icons/24x24/fgo.png0000644000175000017500000000364112304661414016733 0ustar robertrobert00000000000000PNG  IHDRw=sBIT|d pHYs11(RtEXtSoftwarewww.inkscape.org<IDATHkpwƟ^wMvs\=1%!!ФXX) CŪ3΀S:і0i:تb -`K@B $I6^{fd?9y\q (.@*8qfL4 f=I틮S9GUUUij*__sT cVCΙ K$rgL77@v~qۺnV[|W+Rk&7jK 831: ¿cH cl_|ogGc?~՟DD-ѼH83ঀITQW"WQ_ ww+wo["}>7{f\$U1H*a3uXXc "kZ4o%M"jAx_m]΃{+S!h4tA978Im ubQ-u)WV@Uo͢w ų㛃9+_x:'}[f$!ep0dȹQH* K$>q; i~^8;OVBk&,L: Z)T@#yc&R}y wtIr'I@"A;U:g9fҭ" &eܥ@J{zK,H|{U#DK;a\{fFK-8r׃mٹ(H&D`z v1$BS\= ڜu8u @p!z`/Crh3W2$8*9>mi!3H#3oߗ U1P5j-E:X(bFrՑok,LZ:UE#$ȅbbnx]tueZw|'8Olv8PxcC&a`U 6<|.ysO %m$c)L6U|di [7mXmjjKn 7X[dʀX{^>.Weokx Z<Ё(Uelv|#rDUJed(d̬%J ø/JĴ [$cebioE 08S㗏KqV(@_0=)`FZ0Bu0um}}~D p%N 4a D   AIO1\Gv$ %%Ʀ0:VPlLf3#!5f\/;sFB3#0I&?r?t*^܏PD>?Co4ވ+лQ@F B F0D߁^BaCL桡YUhmmnbBR]] "^܏ADXul "!]D]] ӉrivG7x Ԟ}iWww[8~N g "|;!Hx~oڃlXry2wN`ժUfTz8mn:9oWwPE xj{z!`Ψ{:`dx!% %)^=I8lޭYttt&6oвE1q{%X4 Z'"͈p; 7a8k_ZUJ5Q=IR5M(xW w)4M{$N[OC] ƽh۽,66]t/jBWgⱐ?Р!03<4}!]J喱3~{{姝n& EG·p]:N&ពpi/2.`)} nuu1 {Q3:aO7cN{ض+s )O_?/<c&ag6l``G HQO92_;Ͼw Ab8w5UгXDPChM%لTSnuw%K tzW4~uiooWP8R 4fI|4D`?ik:^6 k=$!XފXvc&Gб|yGCGNekz ΫO+uB~"r;V}OG7a2&F#غ+w&yf!-], WDb`5@9@T ;!F`:ښas[笁^T@3\܅rfFft78fcd ΠRq򸕳8\X>OMŇ?PPЉ(J oBi5]G,mCWC9S_?SؒB kq?%gPX88h19(t5"9SlD0چZ`LltJ04Z),H| hJ)Y]@y.ؕ`VU~O\A[7;@Y \S49! ڒ@B&܌Ջ:QM /|}hhI}IH,X)_\,}J>!D(NDG03P`\P!D 㠋wEzڍi+t24Ȳ[|Ѫ^IDzZiŸ'ء<|!D*%D5@*d3"Tn x0v{IfS}4LLvP̳pA:m3lb(Oz@^mDDJ) `9aft"M)W~ٶ dmɨF:A`$&_ڧyǡĘ"#455٬EDrDbnf mC@캮4-a!̯h[Z.?Zwf&jX@J1#~bJLlݯH); T*?22RAG`df+DDy!DlmZ%NףZXk¡3UY0LBiaf`0NY1!>yFDR\T.S$3hDtN(EB;xAJqT^C]>!n.߿Qx`fhEqWriR _ T@ % ]Uof3۽`^Ɇ,U"=Q]8tcփR<j}_yz|h!``%IYP' *Qh:-rͽ?Se ǛbO'=9 ODM,"R%rI /nl<8=u驦d-YuEDyc?7%O<2C9kK~{w@V: wIoA!> ^Cx䟕R+ u]w-@ i{n׿$qRM9?7N6eA!+@ qmx Jf_1grcBY"Ҙ9X5:34M *&=+OJig(}ghJhf2w(GtR:ozaW(WtFjZQT6OE+DU9ND%"*K)]!QuKag7ZF€Ђ& 5J7^O @!K AEX*ȊsKņ ;o5 hM DD fv)Dd !t[J,LjMnz5:7Y z<lV>W(HF` &ؓJ(/xi9 $YQ]B`TYƂڮɷW(]T `ЫKəx#P}DЀp (3Cb! V k`ǃ;U2y@`3U$[tf֘Y!g H2'(I) \4r7NRpwpLI'O)JA"&L~!y7nry@Sc|uƉ7-*/?1 cͲn3_8yMu[ԫDrm 'hYHR^ i~afFZ(w/EzPjCC}>}ÇDccåZ,D"w#=ijǭ_R5+f̆X,j; 4GW2@Ƈ0R5+0 R*h|[6=P[q΅ #, 맙yMSSEM$vC%0ca'!t b ?~7ɩ}lw]/^|Y7577Miv8ĚC3:Y'!fӷ~<9Uyޡtd⮴ќh_ȥu `0p篃i>%(&ȭٶbCDi)"64$6u<>1UJ2 'C1C)TZ-eYGp Z[[#Jv^Vb|S(I}hJ& b03 "vOa.Hˁ5:q !t}^*W cg(!HIq^PZEy  OLbb r~pPUA$x§;XƭBJ҃VH L-*RHX[l16 +&q{YS@X,2I6 c#!464a@hxu^Xǭ\M3!?=rWgwdɂ㎁zOl IKAB6.ydOOO=·DmZB9 uXdI04hÓ|_y!sNG06nT4B39s揘(v0Gjϕ.%$4sZ]lx]$ywLҞ:G1hQJxxNrkw&BQ?; =K|^ ϣH,r}*.Z4U,o5DmQ|G"L}I*7Ġs񨌌#`qwHCfhJ)!D$a ؎ Fr6/0:(O,Dزu'N=JC4DX0"D*VNDz( mPJ $<)YJhض}7& 4dMkkx)O566FoUqb(IsxCC@(bJ6S vPaW]"L`q^ھDG8>oD$RhkMӫaӦ͜Ϗ`pU@0 koߥH$ f b?y+ ^ VU/(lݺ WQFFrG s`'+.E8A,YBr 4xOy;5F?MMO3.zUJrw\ =`ݻ)(f5֬Y!e~nIEfkVㇸլm{K iGcxʰ2FDi^y.OLL12|?YWOgReY=_xOR)f< fn뮻o1nܧǦYG1kD/ǢŝŢsW/im٣\!۶`u]`0فeK{0:}d;=6811T([?ARJT,'Mz(8[7)i2ER|뿞y"&||L@ ?Z>%V! @ 4D Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. fgo/docs/INFORMATION_FOR_TRANSLATORS0000644000175000017500000001246312435141163017515 0ustar robertrobert00000000000000FGo! is using gettext library to localize its interface. Localization files are stored in "data/locale" directory; additionally, other files that can be translated are in: "data/config/", "data/help/", and "share/" folders. You are very welcome to update/improve existing translation, or add a new one if you wish. Even smallest contribution will be appreciated. Here are some helpful (I hope) tips: 1. IF YOU ARE FAMILIAR WITH GETTEXT AND WOULD LIKE TO CONTRIBUTE A NEW, OR MODIFY AN EXISTING TRANSLATION: Localization files are in "data/locale" directory. See section 4 for information about other files that can be translated. When ready, please send "messages.po", and/or other files at a mail address provided at the end of this document. I'll be happy to include you translation in next release of FGo! 2. IF YOU LIKE TO MODIFY EXISTING TRANSLATION BUT NEVER TRANSLATED AN APPLICATION BEFORE: Don't worry, it is quite simple. All you need to do is to edit a "messages.po" file located in "data/locale/LANGUAGE_CODE/LC_MESSAGES" directory. You can do this by using any text editor, but using dedicated software (e.g. Poedit, Virtaal, or Emacs) is more convenient and less error-prone way to edit a PO file. Translation process itself is straightforward and you should have no trouble with the task at all. One thing to remember however, is to include in your translation all "%s", "{0}", or "{1}" formatting characters from the original text. It is a placeholder for some values to be displayed on screen, and its absence can lead to crash of the application. When you're happy with the changes you made to PO file, one last thing to do, is to generate new "messages.mo" file, which is actually used by the program to show translated messages. Some editors have option to generate it for you automatically - if not, you need to do it manually. To do this, open console, navigate to folder containing "messages.po" file and run this command: msgfmt messages.po Note: you may need to install gettext if msgfmt command is not found. In Debian based distributions it is found in the gettext package. Now you can inspect if changes you have made are properly included in FGo! If you're happy with the result, please send "messages.po" file at a mail address provided at the end of this document. I'll be happy to include you translation in next release of FGo! See section 4 for information about other files that can be translated. 3. IF YOU LIKE TO ADD NEW TRANSLATION BUT NEVER TRANSLATED AN APPLICATION BEFORE: First you need to create a new folder in "data/locale" directory. This new folder should have the same name as language code for your language. Some of most common examples of language codes can be found here: http://www.gnu.org/software/hello/manual/gettext/Usual-Language-Codes.html. Next, navigate to this newly created directory and create "LC_MESSAGES" folder. Rest of the translation process is the same as described in section 2, with one exception: you should edit "messages.pot" file in "data/locale" directory, and then save it as "messages.po" into created by you folder. At the end open "messages.po" file with a simple text editor and edit some comments: * At the first line, replace word LANGUAGE with actual name of your language. * At the fourth line, provide your personal data (if you wish of course) as you are the first author of this translation. See section 4 for information about other files that can be translated. 4. SOME ADDITIONAL TEXT TO TRANSLATE: You can also translate files in the "data/config/" and "data/help/". Content from the first directory is shown in Command Line Options Window (the text window at the bottom of the application) at first start of FGo!, and content from the second directory is displayed in the help window. If no file with your language code is present, open file which ends with "en", and save it replacing "en" suffix with your language code - which is the same as the name of directory with your translation in "data/locale". Please take a note that no line in these files should have more than 79 characters. In case of the help file it is mandatory to stay within that 79 characters limit, while in case of the config it is only a general advice and can be exceeded slightly if appropriate. In many text editors, you can enable right margin line which can help you to not exceed this limit. Additionally, you may also check if "share/fgo.desktop" has localization for your language included. 5. TESTING YOUR TRANSLATION: When translation is ready, you can test if all text is properly displayed in the application. Navigate to "~/.fgo" directory, and move out, or rename temporarily file called "config". It will simulate a situation when FGo! is started the first time. After running the program, it should automatically pick up translation based on you system settings. If no proper translation is found, it should revert to default, English language. If you approach any troubles at this point please contact me at a mail address that can be found at the end of this file, I will be happy to help you. Thank you for your help, Robert 'erobo' Leda fgo/docs/INFORMATION_FOR_PACKAGERS0000644000175000017500000000371712434164241017204 0ustar robertrobert00000000000000INFORMATION FOR PACKAGERS = Location = The FGo! release package can be extracted anywhere and the program can be run from that location without having to move files around. For example, FGo! can be installed to a specific user's home directory, and that user will be able to run it right away, provided that the needed dependencies are installed. For system-wide installations of FGo! it is recommended to copy the main fgo/ directory, with its entire content, to /opt, then create a symlink to the application's executable in a location included in the PATH environment variable, most likely /usr/bin. Optionally, a symlink to the configuration file can be created as well in /etc. = Documentation = The program's documentation is stored in fgo/docs and can be either symlinked from /usr/doc/fgo-x.y.z, or copied there. = Icons = Icons are stored in fgo/share/icons and can be either symlinked from or copied to the proper directories in /usr/share/icons. = Desktop = A desktop file, fgo.desktop, is available in fgo/share. It creates a menu entry for FGo! and simplifies the creation of application launchers. It can be either symlinked from or copied to /usr/share/applications and expects the FGo! executable to be available through $PATH, as well as icons to be in the right place. = Presets = The default FGo! settings are quite minimal and the user is required to perform a few minor tasks, like setting the needed paths for FlightGear executables and directories. However, a presets file is available in fgo/data/config, which can be used to specify settings that the packager wants to be enabled by default. For example, in a system-wide installation this will allow every user to run FGo! without having to bother with its basic setup; other FGo! and FlightGear options can also be specified. The presets file is well commented and will be used by FGo! every time a local ~/.fgo/config cannot be found: for instance, this is the case when FGo! is run by the user for the first time. fgo/docs/CHANGE_LOG0000644000175000017500000003133712436637735014775 0ustar robertrobert000000000000000.9 24.09.2009 First public release. 0.91 [BUGFIX] Fixed bug in App.colorText(); all comments in text window should be colored now. [FEATURE] Search can now be started also using Enter key placed in numpad. [BUGFIX] Added clear statement in README file that the program needs PIL library to run. 1.0 [FEATURE] Added a little style to console output; FGo!'s messages are now better distinguished from the rest of the text. [BUGFIX] Kill signal for TerraSync process in App.SaveAndQuit() changed from 15 to 9. [FEATURE] Added better visual feedback when in "carrier mode". [BUGFIX] Fixed bug in App.colorText(); colored text should be displayed properly now with newer Tcl/Tk versions. [FEATURE] Application window is now resizable. Current window resolution is saved to the config file when "Save&Quit" button is clicked. [FEATURE] "Rebuild Airport Database" button added in preferences window. [FEATURE] FGo! will look for data/.config-base file if standard config is not found. ...and many more minor fixes. 1.1 [FEATURE] Translation system has been completely rebuilt. All translations are now stored in "locale" directory. [FEATURE] German translation has been added (thanks to chris_blues). [BUGFIX] "HOME_DIR=" keyword in config file has been replaced by (more appropriate) "FG_WORKING_DIR=" keyword. [FEATURE] To be able to use TerraSync, path to its executable file needs now to be specified in Preferences window. Corresponding "TERRASYNC_BIN=" keyword has been introduced into config file. [FEATURE] TerraSync's default port has been changed to 5501. [BUGFIX] FGo! should not crash anymore if certain keywords with corresponding values are not present in config file. ...some more minor changes and fixes. 1.11 [FEATURE] Spanish translation has been added (thanks to Canseco). [BUGFIX] Wrong "Selected Scenarios" count has been fixed [BUGFIX] Fixed bug in "Save as..." dialog box. File ".fgo" will no longer be generated in main application's directory if "Save as" window is closed without actually saving the config. 1.2 [FEATURE] Dutch translation has been added (thanks to snipey). [BUGFIX] FGo! should now correctly read names of parking positions containing white spaces. [FEATURE] Added new "Airport data source" menu (in Preferences window) where data source for runway or parking positions can be selected. Corresponding "APT_DATA_SOURCE=" keyword introduced into config file. ...and (as usual :) ) some more minor changes and fixes. 1.3.0 [FEATURE] French translation has been added (thanks to Olivier Faivre). [FEATURE] Program structure has been changed: source code, as well as pictures and locale directory has been moved into src folder. [FEATURE] METAR widget added. [FEATURE] Preferences window redesigned. Tabs (well, sort of) are now used to group its content. [FEATURE] Preferences, About and Error window behavior improved. All windows stays on top of main window all the time and main window does not accept input if preferences are open. What's more, it is not possible now to open multiple instances of Preferences and About windows. [BUGFIX] FGo! should not crash anymore if non Latin characters were entered in the paths in Preferences window or in command line options window. [BUGFIX] Airports with non Latin characters in their names should now be visible on the list. This applies to airports: SBGL and TFFC. [FEATURE] All generated by the program data is now stored in "~/.fgo" directory. [FEATURE] ".config-base" file in "data" directory has been moved to "src/config" folder, and renamed to "en". Different variants of the same file are now available for different languages. [FEATURE] A new "presets" file added to "src/config" directory. It can hold predefined settings with which the program will be started if "~/.fgo/config" can't be find. [BUGFIX] "Reset" button will no longer affect "TerraSync" checkbutton state. It could in the past lead to erroneous indications on whether TerraSync is actually enabled or not. [FEATURE] It is now possible for the airport list to display installed airports only. Appropriate controls have been added to Menu ==> Settings. [BUGFIX] Information about runways is now taken from ~/.fgo/apt database, not from scenery itself even if airport data source is set to "Scenery". Apparently, FlightGear is not reading threshold information from scenery even if property: /sim/paths/use-custom-scenery-data is set to true. [FEATURE] A new window is shown to indicate that FlightGear is running. [FEATURE] Help window was added. It is available at Menu ==> Help ==> Help. As a result, old readme file was moved into "src/help" directory, and renamed to "en". Different variants of help file are now available for different languages. [FEATURE] FGo! can now be run from outside its main directory. Symbolic link to fgo file, or command like e.g. "python ~/fgo_main_folder/fgo" can now be used to launch the program from any location. [FEATURE] Starting from now, version indication has been standardized. Every release is now marked with three digit number separated by dots. First two digits describes the version number, when last digit, describes the status of this version: Zero means that such release incorporates important new features, or major source code changes. Any other digit means that this is bug fixing release, or a small update, and it incorporates only small changes to source code, or changes to other files (e.g. translations). ...and (yes, you guessed it right! :D) many more minor changes and fixes. 1.3.1 [BUGFIX] FGo! should not crash if the system language is not supported by the program. 1.4.0 New release after almost three years! [FEATURE] Italian translation has been added (thanks to Philip Lacroix). [BUGFIX] The biggest bug in the program finally fixed! :) Misspelled "FEATUE" tag in this log corrected. It will remain a mystery how I managed to miss that error for so many years. [FEATURE] Search entries are replaced by interactive search entries. [FEATURE] Application's data is moved out the "src" folder to the new "data" directory. [FEATURE] GUI code refactoring - the "gui" module is the "gui" package from now on. [FEATURE] Code handling already obsolete "HOME_DIR=" keyword removed. [FEATURE] PIL module is not mandatory to run FGo! anymore. Without it, however, aircraft thumbnails will not be displayed. [FEATURE] Tooltips added in "Preferences" window. [BUGFIX] Last airport from apt.dat.gz was not included in atp database. [FEATURE] Added message window informing that airport database been generated. Previously, when airport database was generated, whole application just hanged for couple of seconds giving no feedback to a user what is going on. [FEATURE] METAR widget is using proper user agent when sending request for weather report. [FEATURE] In "Preferences" window, a file dialog window initial directory is set to path provided in the corresponding entry field. [FEATURE] Added support for nonstandard aircraft directories. Paths can be set in "Preferences" window under "Additional aircraft path" entry. [BUGFIX] Fixed scenario description been sometimes positioned in top left corner of the screen rather than in top left corner of the app. [FEATURE] Scenario selection refactoring: "Selected Scenarios" label removed and scenario list height increased. [BUGFIX] Fixed "FlightGear is running..." window shown even when FlightGear was not launched properly. [FEATURE] FGo! can track modification time of the apt.dat.gz file and update its own "apt" database when it needs it, "~/.fgo/timestamp" file is used to store modification time information. New "Airport database update" option menu introduced to "Miscellaneous" menu in "Preferences" window. [FEATURE] Scenery prefetch added. [FEATURE] "Preferences" window is now resizable. [FEATURE] Help message added when the program is run with command line options. [FEATURE] Documentation of "src/config/presets" file improved. [FEATURE] FGo! icon slightly changed and icons of different size added. 1.4.1 [BUGFIX] Parking position was not shown if its "number" attribute was not specified in xml. [BUGFIX] Pop-up menus were a little sluggish if search entry had keyboard focus. From now on, focus is taken from any widget when pop-up menu is open. 1.4.2 [FEATURE] German translation updated (thanks to chris_blues). [FEATURE] To avoid confusion, localization files in "data/config" directory are now called: config_de, config_en, etc. and files in "data/help" are called: help_de, help_en, etc. [BUGFIX] All transient windows, except "Preferences" and info window, can now be closed using [Esc] button in case if window manager is not showing close button for transient windows. [BUGFIX] "Select Scenario" button pop-up window closes if main window is moved, resized or minimized. However, it may not work on all window managers. [BUGFIX] Removed option to close "Select Scenario" button pop-up window with middle mouse button. It could accidently copy text from clipboard into command line options window. Use "OK" button instead. 1.4.3 [FEATURE] French translation updated, translation for config and help file added (thanks to f-ojac). 1.4.4 [FEATURE] Italian translation updated, translation for help file added (thanks to Philip Lacroix). 1.4.5 [BUGFIX] Typo in "help_it" file fixed. (Philip Lacroix) [FEATURE] Old Readme file removed. In "docs" directory there is now "README" catalog with symbolic links to readme files for all supported languages. [FEATURE] "icon.svg" added to "data/pics" directory. (Philip Lacroix) [FEATURE] Naming convention for FGo! distribution files changed. From now on, version numbers are separated with dot instead of minus symbol. So archive file looks like this: fgo-1.4.5.tar.gz 1.5.0 [FEATURE] Added support for the new (v 850 and above) apt.dat database format (thanks to Robert). [BUGFIX] Ontario Intl (KONT) airport runways ware not shown in the runway selection list. [BUGFIX] Some airports that had defined parking positions (e.g. KORD) would generate AttributeError when parking button was clicked, not showing any parking list in result. [BUGFIX] Seahawk aircraft was not shown on the aircraft list. [FEATURE] Icon set has been redone. Icons are now moved to a new "data/pics/icons" directory. [FEATURE] Polish translation updated. 1.5.1 [FEATURE] German translation updated (thanks to chris_blues). [FEATURE] French and Italian translation updated (thanks to Philip Lacroix). [FEATURE] Removed program version number in help files. 1.5.2 [FEATURE] Japanese translation added (thanks to AOKI Kiyohito). [FEATURE] Help files updated. Added information about installation in Slackware GNU/Linux. (Philip Lacroix) [BUGFIX] Obsolete "icon.png" name replaced by more up to date "fgo.png" example in help files. 1.5.3 [FEATURE] Added support for custom font size (thanks to Florent Rougon). Corresponding "BASE_FONT_SIZE=" keyword introduced into config file. [BUGFIX] Fixed problem where aircraft list was not generated if any directory in aircraft folder was read protected. [FEATURE] Tooltips reworked, they follow mouse cursor now rather to been static. 1.5.4 [FEATURE] Extended number of widgets in Preferences window with tooltips assigned. [FEATURE] Help file updated. [FEATURE] Polish translation updated. [FEATURE] German translation updated. (chris_blues) [FEATURE] French translation updated. (f-ojac) [FEATURE] Japanese translation updated. (AOKI Kiyohito) [FEATURE] Italian translation updated. (Philip Lacroix) [FEATURE] Spanish translation updated. (Philip Lacroix, Canseco) [FEATURE] Icons moved to a new "share" folder. [FEATURE] "fgo.desktop" file added in "share" folder. (Philip Lacroix) [FEATURE] "INFORMATION_FOR_PACKAGERS" file added in "docs" folder. (Philip Lacroix) [FEATURE] "A WORD TO TRANSLATORS" file updated and renamed. 1.5.5 [BUGFIX] Help window would not open if language in "Preferences" window was not set and no default system language was detected. (Philip Lacroix) [BUGFIX] Fixed typo in "help_pl" file. fgo/docs/README/README_ja0000644000175000017500000001651612433174042015675 0ustar robertrobert00000000000000FGo! - シンプルなFlightGearのGUIランチャー ------------------------------------------------------------------------------- システム要件 OS GNU/Linux FlightGear Python 2.7.8 * TKinter Python Imaging Library (PIL) 1.1.7、 もしくはPillow (PILからの派生) 2.5.1 と ImageTk モジュール ** Tcl/Tk 8.6 * FGo! は Python 2.x 系とのみ互換性があります。 ** 本ライブラリはFGo! を走らせるのに必須ではありませんが、これがないと航空機のサムネイルが表示されません。 ここで提示したソフトウェアのバージョンは開発プロセス中で使用されたものです。FGo!は以前の(もしくは、もっと新しい)バージョンのソフトウェアでも動くかもしれませんが、それらではテストしていません。 ------------------------------------------------------------------------------- インストール: このプログラムはインストールを必要とせず、単にアーカイブを任意の場所に展開してから、初めて起動する前に全てのソフトウェア要件が満たされているか確認してください。 Debian と Debian ベースのディストリビューションでは、以下のパッケージが必要になります: python, python-tk, tcl8.x and tk8.x; オプションで python-imaging-tk もしくは python-pil.imagetk が特定のディストリビューションで利用可能であり、インストールできるでしょう。 簡単にインストールするには、お気に入りのパッケージマネージャで python-imaging-tk か python-pil.imagetk を選択すると、他の必要な物も自動でインストールされます。 Slackware とその互換システムでは、 FGo! のSlackBuild スクリプトが利用可能です。 プログラムをシステムワイドでクリーンにインストールするためにパッケージを生成するためのスクリプトが含まれています。 SlackBuild公式webサイト: http://www.slackbuilds.org からダウンロードできます。 デフォルトのSlackwareシステム(フルインストール)上では、追加で必要になるパッケージはありません。 ------------------------------------------------------------------------------- 実行 プログラムのメインディレクトリにある "fgo" ファイルをクリックするか、コンソールで "./fgo" もしくは"python fgo"コマンドを実行してください。 FlightGearとTerrasyncの出力メッセージにアクセスするために、FGo!をコンソールで実行することを推奨します。 ------------------------------------------------------------------------------- 構成 初めて FGo! を使うときには設定が必要です。 メニューバーの「設定」から、詳細設定ウインドウを開き、空欄へ適切に入力してください。"FlightGear の設定" タブ内の初めの3つの項目は、プログラムを正常に動作させるために重要です。 - 残りは任意です。 Tip: オプション欄にマウスカーソルを重ねると、詳細が表示されます。 "FlightGear の設定" タブを変更して"設定を保存"をクリックすれば直ちに反映されますが、それ以外の変更を反映させるには、FGo! の再起動が必要です。 ------------------------------------------------------------------------------- メインメニューのアイテム ファイル: 読み込み - 指定した設定ファイルを読み込みます。 保存 - 指定したファイルに設定を保存します。 保存して終了 - 設定を保存しアプリケーションを終了します。 終了 - アプリケーションを終了します。 設定: インストール済みの空港のみ表示 - 現在HDDに実際にインストールされている シーナリーにある空港のみ、一覧に表示します。 インストール済みの空港一覧を更新 - HDD内にインストールされているシーナリーを スキャンして、空港の一覧を更新します。 "インストール済みの空港のみ表示" を選択しているときのみ動作します。 詳細設定 - 詳細設定画面を開きます。 ツール: METAR - 選択した(あるいは至近の)空港の気象通報を表示します。 この気象通報は http://weather.noaa.gov/ からダウンロードします。 ヘルプ: ヘルプ - ヘルプウインドウを開きます。 このプログラムについて - "このプログラムについて"のウインドウを開きます。 ------------------------------------------------------------------------------- コマンドラインオプション ウインドウ 底部にあるテキストウインドウに、FlightGearに伝えたいコマンドラインオプションを書くことができます。 いくつかのオプションはデフォルトで提供されますが、より多くの例はFlightGearのドキュメンテーションを参照するか、Wiki (http://wiki.flightgear.org/index.php/Command_Line_Options) をチェックしてください ------------------------------------------------------------------------------- TIPS&TRICKS * 空港データのソースが"シーナリー"にセットされている場合、対応するシーナリーがインストールされるまで空港の駐機場情報が利用できない場合があります。 *空母から飛行をスタートすることもできます。 パネル中央の、現在選択中の空港のICAOコードが表示されている部分(航空機の画像の下)をクリックして、利用する艦を選択してください。 表示がICAOコードから、選択中の艦の名前に変わり、青くハイライト表示され、"空母モード"を示すでしょう。対応するシナリオが自動的に選ばれます。 空母名のところをクリックし、ポップアップするリストから"None"を選択することで、再び空港を選ぶことができます。 * "シナリオを選択" のリストでは、(利用可能な場合)右クリックするとシナリオの概要を見ることができます。 * ウインドウの解像度は"保存して終了" ボタンを押すと保存されます。 ------------------------------------------------------------------------------- 既知のバグと制限 * FGo! は新しいTerraSyncのインスタンスを開始する前に、現在のTerraSyncが本当に終了したか確認しません。 もし"TerraSync" チェックボタンがチェックされておらず、短い間に再びチェックを入れた場合、"error binding to port 5501" メッセージがコンソールに表示されます。 これは古いTerraSyncのインスタンスがデータのダウンロードを終えていない事を示しています。 この場合、ダウンロードが終わるのを待ってから、"TerraSync" チェックボタンをチェックしてください。 * 非常に長い駐機場名は駐機場ボタンの枠に収まりません。 ------------------------------------------------------------------------------- このソフトウェアを利用していただきありがとうございます。 Robert 'erobo' Leda fgo/docs/README/README_fr0000644000175000017500000001544012430471320015701 0ustar robertrobert00000000000000FGo! - Une interface de lancement simple pour FlightGear. ------------------------------------------------------------------------------- EXIGENCES - Système d'exploitation : GNU/Linux - FlightGear - Python 2.7.8 * - TKinter - Python Imaging Library (PIL) 1.1.7 ou Pillow (fork de PIL) 2.5.1 avec le module ImageTK ** - Tcl/Tk 8.6 * FGo! est compatible uniquement avec Python 2.x. ** Cette librairie n'est pas indispensable pour lancer FGo! mais les miniatures des aéronefs ne seront pas affichées sans elle. Les numéros donnés içi correspondent aux versions du logiciel utilisées lors du processus de développement. Il est probable que FGo! puisse fonctionner avec des versions plus anciennes (ou récentes), mais le programme n'a pas été testé avec elles. ------------------------------------------------------------------------------- INSTALLATION Ce programme ne nécessite aucune installation, décompressez simplement l'archive dans un emplacement donné et assurez-vous que toutes les dépendances logicielles sont bien installées avant le premier lancement. Pour Debian et les distributions basées sur Debian, les paquetages suivants sont nécessaires: python, python-tk, tcl8.x et tk8.x; éventuellement, python-imaging-tk ou python-pil.imagetk, en fonction de celui qui est disponible sur une distribution particulière, peut être installé. Pour une installation simple, choisissez python-imaging-tk ou python-pil.imagetk dans votre gestionnaire de paquets préféré et toutes les autres dépendances devraient être installées automatiquement. Pour Slackware et systèmes compatibles, un SlackBuild pour FGo! est disponible. Ceci va générer un paquet qui permet une installation de FGo! intégrée dans le système. Le SlackBuild peut être téléchargé sur le site officiel du projet: http://www.slackbuilds.org. Pour les systèmes Slackware standard (full) aucun paquet supplémentaire n'est nécessaire. ------------------------------------------------------------------------------- LANCEMENT Cliquez sur le fichier "fgo" dans le répertoire principal du programme, ou lancez-le dans une console avec la commande "./fgo" ou "python fgo". Lancer FGo! dans une console est recommandé, car cela donne accès aux messages d'évènement de FlightGear et TerraSync. ------------------------------------------------------------------------------- CONFIGURATION Afin d'utiliser FGo!, vous devez d'abord le configurer. Ouvrez la fenêtre de préférences (dans le menu, choisissez: Paramètres => Préférences) et remplissez les champs vides. Le remplissage des trois premières entrées est nécessaire pour un fonctionnement nominal du programme; le reste est optionnel. Astuce: placez le curseur de la souris au-dessus d'une option pour voir sa description Les changements dans l'onglet "Paramètres FlightGear" sont appliqués immédiatement après avoir cliqué sur le bouton "Enregistrer les paramètres", cependant les changements dans les autres onglets peuvent nécessiter de redémarrer FGo! pour qu'ils soient pris en compte. ------------------------------------------------------------------------------- ITEMS DU MENU PRINCIPAL Fichier: Charger - charger un fichier de configuration spécifique. Enregistrer sous... - sauvegarde les paramètres dans le fichier de configuration précisé. Enregistrer et quitter - sauvegarde les paramètres et quitte l'application. Quitter - quitte l'application. Paramètres: Afficher uniquement les aéroports installés - seuls les aéroports présents dans les scènes actuellement installées sur le disque dur sont affichés dans la liste des aéroports. Mettre à jour la liste des aéroports installés - recherche sur le disque dur les scènes installées et met à jour la liste des aéroports. Ceci fonctionne uniquement si l'option "Afficher uniquement les aéroports installés" est activée. Préférences - affiche la fenêtre des préférences. Outils: METAR - affiche le rapport METAR pour l'aéroport sélectionné (ou le plus proche). Ces rapports sont téléchargés de l'adresse: http://weather.noaa.gov/ Aide: Aide - ouvre la fenêtre "Aide". A propos - ouvre la fenêtre "A propos de". ------------------------------------------------------------------------------- FENETRE OPTIONS DE LIGNE DE COMMANDE Dans la fenêtre de texte du bas, vous pouvez écrire des options de ligne de commande qui seront passées à FlightGear. Peu d'options sont proposées par défaut, pour plus d'exemples consultez la documentation de FlightGear ou le Wiki à l'adresse: http://wiki.flightgear.org/index.php/Command_Line_Options ------------------------------------------------------------------------------- TRUCS & ASTUCES * Si la source des données d'aéroport a été positionnée à "Scènes", alors l'information sur les positions de parking d'aéroport peut ne pas être disponible jusqu'à ce que les scènes correspondantes soient installées. * Vous pouvez démarrer un vol à partir d'un porte-avions. Dans le panneau du milieu, cliquez sur le code ICAO de l'aéroport actuel (juste en-dessous de l'image de l'aéronef) et choisissez parmi les porte-avions disponibles. Le code ICAO sera alors modifié pour afficher le nom du bateau et sera surligné en bleu pour indiquer que vous êtes à présent en mode "porte-avions". Le scénario correspondant sera choisi automatiquement. Pour pouvoir choisir à nouveau des aéroports, vous devez cliquer sur le nom du porte-avions et, à partir de la liste de la fenêtre, choisissez "Aucun". * Dans la liste "Sélectionnez un scénario", vous pouvez effectuer un clic droit sur n'importe quel scénario pour obtenir sa description (si elle est disponible). * La résolution de la fenêtre est sauvegardée lorsque le bouton "Enregistrer et quitter" est cliqué. ------------------------------------------------------------------------------- ANOMALIES CONNUES ET LIMITATIONS * FGo! ne sait pas si TerraSync a été effectivement fermé avant qu'une nouvelle instance n'en soit lancée. Si la case à cocher "TerraSync" est décochée puis à nouveau cochée sur une courte période de temps, le message "error binding to port 5501" peut s'afficher dans une console. Il indique qu'une ancienne instance de TerraSync n'a pas encore fini de télécharger les données. Dans ce cas, attendez jusqu'à ce que le téléchargement soit terminé et cochez à nouveau la case "TerraSync". * Les noms de places de parking très longs ne rentrent pas dans le bouton parking. ------------------------------------------------------------------------------- Merci d'utiliser ce logiciel, Robert 'erobo' Leda fgo/docs/README/README_pl0000644000175000017500000001424112430720202015677 0ustar robertrobert00000000000000FGo! - Prosty program do uruchamiania symulatora FlightGear. ------------------------------------------------------------------------------- WYMAGANIA - System operacyjny GNU/Linux - FlightGear - Python 2.7.8 * - TKinter - Python Imaging Library (PIL) 1.1.7 lub Pillow (fork PIL'a) 2.5.1 z modułem ImageTk ** - Tcl/Tk 8.6 * FGo! jest kompatybilne tylko z Pythonem 2.x. ** Biblioteka ta nie jest wymagana do uruchomienia programu, jednak bez niej nie będą wyświetlane miniatury samolotów. Podane tutaj numery wersji oprogramowania odpowiadają wersjom użytym przy tworzeniu programu, jest możliwe, że FGo! będzie działał ze starszymi (lub nowszymi) wydaniami, nie został jednak z nimi przetestowany. ------------------------------------------------------------------------------- INSTALACJA Program nie wymaga instalacji, przed pierwszym uruchomieniem należy się jednak upewnić się, że wszystkie wymagane zależności zostały zainstalowane. W Debianie, oraz dystrybucjach na nim opartych wymagane są następujące pakiety: python, python-tk, tcl8.x i tk8.x; oraz opcjonalnie (w zależności od dystrybucji): python-pil.imagetk lub python-imaging-tk. W menadżerze pakietów wystarczy wybrać pakiet python-pil.imagetk lub python-imaging-tk, reszta zależności powinna zostać zainstalowana automatycznie razem z nim. Dla Slackware i kompatybilnych systemów dostępny jest SlackBuild dla FGo!. Skrypt ten generuje pakiet, który pozwala na instalację programu dla całego systemu. Można go pobrać na oficjalnej stronie: http://www.slackbuilds.org. W standardowym systemie Slackware (full) nie ma potrzeby instalowania dodatkowych paczek. ------------------------------------------------------------------------------- URUCHAMIANIE Kliknij plik "fgo" w katalogu głównym programu, lub uruchom w konsoli poleceniem "./fgo" lub "python fgo". Uruchamianie FGo! w konsoli jest zalecane, gdyż daje dostęp do komunikatów wyświetlanych przez FlightGear i TerraSync. ------------------------------------------------------------------------------- KONFIGURACJA Zanim będziesz mógł korzystać z programu, powinieneś go skonfigurować. Otwórz okno preferencji (Menu Ustawienia => Preferencje) i wypełnij puste pola. Do poprawnego działania programu potrzebne jest wypełnienie pierwszych trzech pól w zakładce "Ustawienia FlightGear", reszta ustawień jest opcjonalna. Podpowiedź: Umieść kursor myszy nad elementem aby zobaczyć jego opis. Zmiany ustawień w zakładce "Ustawienia FlightGear" są uwzględniane przez FGo! natychmiast po kliknięciu przycisku "Zapisz ustawienia", zmiany w pozostałych zakładkach mogą jednak wymagać ponownego uruchomienia programu. ------------------------------------------------------------------------------- ELEMENTY GŁÓWNEGO MENU Plik: Wczytaj - wczytaj zapisany wcześniej plik konfiguracyjny. Zapisz jako... - zapisz aktualne ustawienia do pliku. Zapisz i Wyjdź - zapisz ustawienia i zamknij program. Zakończ - zamknij program. Ustawienia: Pokaż tylko zainstalowane lotniska - na liście lotnisk będą pokazywane tylko lotniska zainstalowane na twardym dysku. Odśwież listę zainstalowanych lotnisk - przeskanuj twardy dysk w poszukiwaniu zainstalowanej scenerii i odśwież listę lotnisk. Opcja ta działa tylko w przypadku, gdy opcja "Pokaż tylko zainstalowane lotniska" jest aktywna. Preferencje - otwórz okno preferencji. Narzędzia: METAR - pokaż raport pogodowy dla wybranego (lub pobliskiego) lotniska. Raporty te są pobierana ze strony http://weather.noaa.gov/ Pomoc: Pomoc - otwórz okno pomocy. O programie - otwórz okno z informacjami o programie. ------------------------------------------------------------------------------- OKNO LINII POLECEŃ W oknie tekstowym umieszczonym na dole głównego okna aplikacji można wpisywać opcje, z którymi zostanie uruchomiony FlightGear. Parę opcji jest tam umieszczonych już przy pierwszym starcie programu. Więcej przykładów można znaleźć w dokumentacji FlightGear, lub na wiki, pod adresem: http://wiki.flightgear.org/index.php/Command_Line_Options ------------------------------------------------------------------------------- TIPS&TRICKS * Jeżeli źródłem danych o lotniskach jest sceneria, to informacje o pozycjach parkingowych mogą być niedostępne dla wybranego lotniska do czasu zainstalowania odpowiedniej scenerii. * Możesz wystartować na pokładzie lotniskowca. Kliknij na kodzie lotniska znajdującym się na środkowym panelu (zaraz pod obrazkiem przedstawiającym wybrany samolot) i wybierz któryś z dostępnych okrętów. Kod lotniska zostanie zastąpiony nazwą wybranej jednostki i zmieni kolor na niebieski, by zaznaczyć, że program znajduje się teraz w trybie wyboru lotniskowca. Dodatkowo, FGo! automatycznie wybierze odpowiedni dla danego okrętu scenariusz. Aby móc z powrotem startować z lotnisk, należy kliknąć nazwę lotniskowca i z listy która się ukaże, wybrać opcję "Żaden". * Na liście scenariuszy można kliknąć prawym przyciskiem myszki na każdym z nich, aby zobaczyć jego opis (o ile opis ten jest dostępny). * Gdy zostanie użyta opcja "Zapisz i Wyjdź", FGo! zapamięta rozmiar swojego okna. ------------------------------------------------------------------------------- ZNANE BŁĘDY I OGRANICZENIA * FGo! nie śledzi czy sesja TerraSync została zakończona. Gdy TerraSync zostanie w krótkim odstępie czasu wyłączony i ponownie wyłączony, w konsoli może pojawić się następujący komunikat: error binding to port 5501. Wskazuje on na to, że nowa sesja TerraSync nie może zostać uruchomiona ponieważ poprzednia sesja nie zakończyła jeszcze ściągania scenerii z internetu. W takiej sytuacji należy poczekać, aż download dobiegnie końca i ponownie zaznaczyć pole TerraSync. * Długie nazwy pozycji parkingowych nie mieszczą się w przycisku Parking. ------------------------------------------------------------------------------- Dziękuję za używanie tego programu, Robert 'erobo' Leda fgo/docs/README/README_de0000644000175000017500000001455712430760174015702 0ustar robertrobert00000000000000FGo! - Ein einfacher GUI Starter für FlightGear. ------------------------------------------------------------------------------- SYSTEMVORAUSSETZUNGEN - Betriebssystem GNU/Linux - FlightGear - Python 2.7.8 * - TKinter - Python Imaging Library (PIL) 1.1.7 oder Pillow (PIL fork) 2.5.1 mit ImageTk Modul ** - Tcl/Tk 8.6 * FGo! ist nur mit Python Serie 2.x kompatibel. ** Diese Bibliothek ist nicht unbedingt notwendig um FGo! zu starten aber die Flugzeug-Vorschau wird ohne sie nicht angezeigt. Diese Versionsnummern stellen die Versionen der verwendeten Softwaredar, die zur Entwicklung genutzt wurden. Es ist durchaus möglich, daß FGo! auch mit älteren (oder neueren) Versionen funktioniert, aber das Programm wurde damit nicht getestet. ------------------------------------------------------------------------------- INSTALLATION FGo! braucht nicht installiert zu werden, man braucht nur das Archiv zu entpacken und sicherzustellen, daß die Anforderungen erfüllt wurden. In Debian und Debian-basierten Distributionen werden die folgenden Pakete gebraucht: python, python-tk, tcl8.x and tk8.x; optional python-imaging-tk oder python-pil.imagetk, abhängig davon, welches Paket verfügbar ist auf der jeweiligen Distribution. Für eine einfache Installation wähle python-imaging-tk oder python-pil.imagetk im bevorzugten Paketmanager, und alle anderen benötigten Abhängigkeiten sollten automatisch mit installiert werden. Für Slackware-basierte Systeme steht ein SlackBuild Paket zu Verfügung. Das enthaltene Skript generiert ein kompatibles Slackware Paket, das eine saubere, systemweite Installation von FGo! ermöglicht. Das SlackBuild kann von der Website http://www.slackbuilds.org heruntergeladen werden. Für standard Slackware Installationen (full) ist kein zusätzliches Paket nötig. ------------------------------------------------------------------------------- GESTARTET Klicke auf die Datei "fgo" im Hauptverzeichnis des Programms, oder starte es aus einer Konsole mit entweder "./fgo" oder "python fgo". Es wird empfohlen FGo! aus einer Konsole zu starten, da man sonst die Ausgaben von Flightgear und TerraSync nicht sehen kann. ------------------------------------------------------------------------------- KONFIGURATION Um FGo! benutzen zu können, muss man es erst einrichten. Öffnen Sie das Einstellungsfenster (im Menü: Einstellungen => Eigenschaften) und füllen Sie die leeren Felder aus. Das Ausfüllen der ersten drei Felder ist notwendig für die korrekte Ausführung des Programmes - der Rest ist optional. Hinweis: Um eine Beschreibung der jeweiligen Option zu sehen, halten sie die Maus kurz über dem entsprechenden Feld. Veränderungen in den Einstellungen werden nach "Einstellungen speichern" sofort angewendet, allerdings benötigen Veränderungen in den TerraSync- Einstellungen und Spracheinstellungen einen Neustart von FGo!. ------------------------------------------------------------------------------- HAUPTMENÜ EINTRÄGE Datei: Laden - Lade eine vorhandene Konfigurations-Datei Speichern unter... - Speichere Einstellungen in eine Konfigurations-Datei Speichern und beenden - Speichere Einstellungen und beende die Anwendung Beenden - Beende die Anwendung ohne zu speichern Einstellungen: Nur installierte Flughäfen anzeigen - Es werden nur die tatsächlich auf der Festplatte vorhandene Flughäfen angezeigt. Liste der Flughäfen auffrischen - Durchsuche die Festplatte nach installierter Szenerie und baue eine neue Flughafen-Liste. Das funktioniert nur wenn "Nur installierte Flughäfen anzeigen" angewählt ist. Eigenschaften - öffne das Eigenschaften-Fenster. Werkzeuge: METAR - zeige METAR Report für den gewählten (oder nahesten) Flughafen. Dieser wird von http://weather.noaa.gov/ heruntergeladen. Hilfe: Hilfe - Öffne dieses Hilfe-Fenster. Über - Öffne das "Über"-Fenster. ------------------------------------------------------------------------------- KOMMANDOZEILEN OPTIONEN FENSTER Im Text-Fenster am unteren Ende des FGo!-Fensters kann man Kommandozeilen- Optionen angeben, die an FlightGear weitergereicht werden. Es werden vorerst nur wenige Optionen angezeigt. Für mehr Informationen und Beispiele bemühen Sie bitte die FlightGear-Dokumentation, prüfen Sie im Terminal "fgfs --help --verbose" oder sehen Sie im Wiki nach: http://wiki.flightgear.org/index.php/Command_Line_Options ------------------------------------------------------------------------------- TIPS&TRICKS * Wenn die Flughafen-Datenquelle auf "Szenerie" gesetzt ist, können Informationen über Parkpositionen solange nicht verfügbar sein, bis die entsprechende Szenerie installiert ist. * Man kann auch von einem Flugzeugträger starten. Klicken Sie im mittleren Abschnitt auf den ICAO-Code des zuletzt gewählten Flughafens (direkt unter dem Flugzeugbild) und wählen Sie eins der verfügbaren Schiffe. Der ICAO-Code wird daraufhin durch den Namen des gewählten Flugzeugträgers ersetzt und blau hervorgehoben, um anzuzeigen dass Sie sich jetzt im "Flugzeugträger- Modus" befinden. Das entsprechende Szenario wird automatisch gewählt. Um wieder auf die Flughäfen zugreifen zu können müssen Sie nur auf den Flugzeugträgernamen klicken und "keine" auswählen. * In der Liste "Szenario auswählen" können Sie sich, mit einem Rechtsklick auf ein Szenario, die Beschreibung dessen ansehen (falls verfügbar). * Die Fenstergröße wird gespeichert, wenn "Speichern und beenden" betätigt wird. ------------------------------------------------------------------------------- BEKANNTE FEHLER UND GRENZEN * FGo! weiß nicht, ob TerraSync tatsächlich beendet wurde, bevor eine neue Instanz gestartet wird. Wenn das "TerraSync" Ankreuzfeld geleert und gleich wieder angekreuzt wird, kann die Meldung "error binding to port 5501" im Terminalfenster erscheinen. Dies zeigt an, dass eine alte Instanz von TerraSync noch nicht fertig ist mit dem Herunterladen der Daten. Warten Sie in diesem Fall bis das Herunterladen beendet wurde und kreuzen Sie das "TerraSync"-Feld danach wieder an. * Sehr lange Parkplatznamen passen nicht in den Parkplatz-Knopf. ------------------------------------------------------------------------------- Vielen Dank, dass Sie diese Software benutzen, Robert 'erobo' Leda fgo/docs/README/README_en0000644000175000017500000001301712430471320015672 0ustar robertrobert00000000000000FGo! - A simple GUI launcher for FlightGear. ------------------------------------------------------------------------------- REQUIREMENTS - Operating system GNU/Linux - FlightGear - Python 2.7.8 * - TKinter - Python Imaging Library (PIL) 1.1.7 or Pillow (PIL fork) 2.5.1 with ImageTk module ** - Tcl/Tk 8.6 * FGo! is compatible only with Python 2.x. ** This library is not mandatory to run FGo! but aircraft thumbnails will not be displayed without it. Numbers given here correspond to versions of the software used in development process. It's quite likely for FGo! to work with older (or newer) versions, but the program was not tested with them. ------------------------------------------------------------------------------- INSTALLATION This program requires no installation, just unpack the archive anywhere and make sure that all software requirements are met before the first start. In Debian and Debian based distributions the following packages are required: python, python-tk, tcl8.x and tk8.x; optionally python-imaging-tk or python-pil.imagetk, depending on which is available on particular distribution, may be installed. For easy installation choose python-imaging-tk or python-pil.imagetk in your favorite package manager, and all other required dependencies should be installed automatically. For Slackware and compatible systems a SlackBuild for FGo! is available. The included script will generate a package that allows for a clean, system-wide installation of the program. The SlackBuild can be downloaded from the official website: http://www.slackbuilds.org. On default Slackware systems (full) no additional packages are required. ------------------------------------------------------------------------------- RUNNING Click "fgo" file in the main directory of the program, or run it in a console with the "./fgo" or "python fgo" command. Running FGo! in a console is recommended as it gives access to FlightGear and TerraSync output messages. ------------------------------------------------------------------------------- CONFIGURATION In order to use FGo! you need to first set it up. Open preferences window (in menu choose: Settings => Preferences) and fill in empty entry fields. Filling the first three entries in "FlightGear settings" tab is necessary for proper operation of the program - the rest is optional. Tip: Hover mouse cursor over any option to see its description. Changes in "FlightGear settings" tab are applied immediately after clicking on "Save Settings" button, however changes in other tabs may require restarting FGo! for them to be applied. ------------------------------------------------------------------------------- MAIN MENU ITEMS File: Load - load specified config file. Save as... - save settings to specified config file. Save & Quit - save settings and quit the application. Quit - quit the application. Settings: Show installed airports only - only airports present in scenery actually installed on the hard drive will be shown on the airport list. Update list of installed airports - scan hard drive for installed scenery and update the airport list. It works only if "Show installed airports only" is selected. Preferences - open preferences window. Tools: METAR - show METAR report for selected (or nearest) airport. These reports are downloaded from http://weather.noaa.gov/ Help: Help - open "Help" window. About - open "About" window. ------------------------------------------------------------------------------- COMMAND LINE OPTIONS WINDOW In text window at the bottom, you can write command line options that will be passed to FlightGear. Few options are provided by default, for more examples consult FlightGear documentation or check Wiki at: http://wiki.flightgear.org/index.php/Command_Line_Options ------------------------------------------------------------------------------- TIPS&TRICKS * If airport data source was set to "Scenery", then information about airport's park positions may not be available until corresponding scenery is installed. * You can start a flight from an aircraft carrier. In the middle panel click on current airport ICAO code (right under aircraft picture) and choose one from available ships. ICAO code will then change to selected ship's name, and will be highlighted blue to indicate that you're now in "carrier mode". Corresponding scenario will be selected automatically. To be able to choose airports again, you need to click on carrier name, and from pop-up list select "None". * In "Select Scenario" list, you can right click on any scenario to see its description (if available). * Window resolution is saved when "Save&Quit" button is clicked. ------------------------------------------------------------------------------- KNOWN BUGS AND LIMITATIONS * FGo! does not track if TerraSync has been actually closed before starting a new instance of it. If "TerraSync" checkbutton is unchecked and checked again in short period of time, "error binding to port 5501" message may be shown in a console. It indicates that old instance of TerraSync does not finished downloading data yet. In that case, wait until download is finished and check "TerraSync" checkbutton again. * Very long parking places names does not fit in parking button. ------------------------------------------------------------------------------- Thank you for using this software, Robert 'erobo' Leda fgo/docs/README/README_es0000644000175000017500000001325712432674071015717 0ustar robertrobert00000000000000FGo! - Un simple GUI lanzador para FlightGear. ------------------------------------------------------------------------------- REQUISITOS - Sistema Operativo GNU/Linux - FlightGear - Python 2.7.8 * - TKinter - Python Imaging Library (PIL) 1.1.7 o Pillow (fork de PIL) 2.5.1 con el modulo ImageTk ** - Tcl/Tk 8.6 * FGo! solo es compatible con Python 2.x. ** Esta biblioteca non es obligatoria, pero sin ella las miniaturas de los aviones no serán visualizadas. ------------------------------------------------------------------------------- INSTALACIÓN Este programa no precisa instalación: la extracción de el fichero archivio es suficiente, pero los paquetes necesarios deben ser instalados antes de ejecutar por primera vez Fgo!. En Debian y en las distribuciones basadas en Debian los paquetes necesarios serian python, python-tk, python-imaging-tk, tcl8.x y tk8.x. Los paquetes opcionales serian python-imaging-tk o python-pil.imagetk, dependiendo de la distribucion en particular. Puedes simplemente seleccionar python-imaging-tk o python-pil.imagetk: el gestor de paquetes instalará el resto de dependencias requeridas. Un SlackBuild para FGo! está disponible para Slackware GNU/Linux y los sistemas compatibles. El script incluido en el Slackbuild puede generar un paquete que permite una instalación del programa integrada en el sistema. El SlackBuild se puede descargar desde el sitio http://www.slackbuilds.org. Las instalaciones estándar de Slackware (full) no requieren paquetes adicionales. ------------------------------------------------------------------------------- EJECUCION Navega al directorio donde FGo! fue descomprimido y clic en el fichero "fgo", o escribe "python fgo" o "./fgo" en la teminal. El mejor método para ejecutar la aplicación es hacerlo a través de una terminale, por ver los mensajes de FlightGear y de TerraSync. ------------------------------------------------------------------------------- CONFIGURACIÓN Para poder usar FGo! primero necesitas configurarlo. Selecciona "Ajustes" => "Preferencias" y rellena los campos vacíos. Rellenar las tres primeras entradas en "Ajustes de FlightGear" es necesario para el correcto funcionamiento del programa; el resto es opcional. Truco: mover el cursor en las opciones para leer las descripciones. Los cambios en los ajustes de FlightGear son inmediatamente aplicados después de pulsar "Guardar ajustes", pero los cambios en "Ajustes de TerraSync" y "Miscelaneo" pueden requerir cerrar y abrir de nuevo FGo!. ------------------------------------------------------------------------------- OPCIONES DEL MENU PRINCIPAL Archivo: Cargar - cargar fichero de configuración. Guardar como... - guardas ajustes en un fichero especifico. Guardar y Salir - guarda los ajustes y cierra el programa. Salir - salir de la aplicación. Ajustes: Muestra solo aeropuertos instalados - solo aeropuertos presentes en el directorio scenery actualmente instalados en el disco duro, se mostraran en la lista de aeropuertos. Actualizar lista de aeropuertos instalados - escanea el disco duro en busca de escenarios instalados y actualiza la lista. Solo funciona si "Mostrar aeropuertos instalados" es seleccionado. Preferencias - Abre la ventana preferencias. Herramientas: METAR - muestra el reporte METAR para el aeropuerto (o el mas cercano) seleccionado. Estos reportes se descargan desde http://weather.noaa.gov/ Ayuda: Ayuda - Abre la ventana "Ayuda". Acerca de - Abre la ventana "Acerca de". ------------------------------------------------------------------------------- VENTANA DE LINEA DE COMANDOS En la ventana de texto puedes escribir lineas de comando que serán ejecutadas por FlightGear. Hay pocas opciones provistas por defecto, para encontrar mas ejemplos, consulta la documentación de FlightGear o visita la Wiki en: http://wiki.flightgear.org/index.php/Command_Line_Options ------------------------------------------------------------------------------- TRUCOS Y CONSEJOS * Si el origen de datos de aeropuertos fue configurado como "Escenarios", la información acerca de las posiciones de parking no estarán disponibles hasta que el correspondiente escenario sea instalado * Puedes empezar a volar desde un portaaviones. Pulsa la opción que esta debajo de la imagen del avión seleccionado y escoge uno de los portaaviones disponibles. El código ICAO cambiara el nombre del portaaviones y aparecerá de color azul para indicar que estas en "Modo Portaaviones". El escenario correspondiente sera selecciona automáticamente. Para poder elegir un aeropuerto de nuevo, necesitas pulsar en el nombre del portaaviones y seleccionar "Ninguno". * En la lista "Seleccionar Escenario" puedes pulsar el botón derecho para ver la descripción. * La resolución se guarda cuando usas la opción "Guardar y Salir". ------------------------------------------------------------------------------- ERRORES CONOCIDOS Y LIMITACIONES * FGo! no vigila si TerraSync ha sido cerrado antes de empezar una nueva sesión. Si la casilla de verificación "TerraSync" no esta seleccionada y se vuelva a seleccionar en un corto espacio de tiempo, el mensaje "error binding to port 5501" aparecerá en la terminal. Indica que la instancia anterior de TerraSync no acabo de descargar los datos. En este caso, espera a que acabe de descargar y selecciona "Terrasync" de nuevo. * Nombres de parking demasiados largos no caben en el botón parking. ------------------------------------------------------------------------------- Gracias por usar este programa, Robert 'erobo' Leda fgo/docs/README/README_it0000644000175000017500000001457312430471320015714 0ustar robertrobert00000000000000FGo! - Un semplice programma con interfaccia grafica per avviare FlightGear ------------------------------------------------------------------------------- REQUISITI - Sistema operativo GNU/Linux - FlightGear - Python 2.7.8 * - TKinter - Python Imaging Library (PIL) 1.1.7 o Pillow (fork di PIL) 2.5.1 con il modulo ImageTk ** - Tcl/Tk 8.6 * FGo! è compatibile solo con Python 2.x. ** Non è obbligatorio avere questa libreria per utilizzare FGo!, tuttavia senza di essa le miniature degli aerei non verranno visualizzate. I numeri di versione qui specificati corrispondono al software utilizzato per lo sviluppo del programma. È molto probabile che FGo! funzioni anche con versioni precedenti (o successive) ma con esse non è stato testato. ------------------------------------------------------------------------------- INSTALLAZIONE Questo programma non richiede installazione: è sufficiente estrarre l'archivio dove si desidera, assicurandosi che il software necessario sia stato installato prima che FGo! venga avviato per la prima volta. In Debian e nelle distribuzioni basate su Debian è necessario che i seguenti pacchetti siano installati: python, python-tk, tcl8.x e tk8.x; a seconda della distribuzione in uso è inoltre possibile installare python-imaging-tk, oppure python-pil.imagetk. Per un'installazione semplice, scegliere python-imaging-tk o python-pil.imagetk mediante il proprio gestore dei pacchetti preferito; tutte le dipendenze necessarie dovrebbero venire aggiunte automaticamente. Per Slackware e i sistemi compatibili è disponibile uno SlackBuild, con cui è possibile generare, a partire dall'archivio originale di FGo!, un pacchetto che permette un'installazione del programma integrata nel sistema. Lo SlackBuild è scaricabile dal sito http://www.slackbuilds.org. Sui sistemi Slackware standard (full) non è necessario installare del software supplementare. ------------------------------------------------------------------------------- UTILIZZO Cliccare sul file "fgo" che si trova nella directory principale del programma, oppure avviarlo da terminale con il comando "./fgo" o "python fgo". L'avvio da terminale è consigliato, poiché rende visibili i messaggi di FlightGear e di TerraSync. ------------------------------------------------------------------------------- CONFIGURAZIONE Per poter utilizzare FGo! occorre dapprima configurarlo. Aprire la finestra delle impostazioni (scegliere "Preferenze" dal menu "Impostazioni") e riempire i campi vuoti. Per poter utilizzare il programma è necessario riempire i primi tre campi nella sezione "Impostazioni di FlightGear"; il resto è facoltativo. Suggerimento: muovere il cursore del mouse sulle varie opzioni per leggerne la descrizione. Le modifiche alla sezione "Impostazioni di FlightGear" vengono applicate subito dopo aver cliccato sul pulsante "Salva impostazioni", mentre per modifiche alle altre sezioni può essere necessario il riavvio del programma. ------------------------------------------------------------------------------- VOCI DEL MENU PRINCIPALE File: Carica - carica il file di configurazione specificato. Salva come... - salva le modifiche nel file di configurazione specificato. Salva ed esci - salva le modifiche ed esce dal programma. Esci - esce dal programma. Impostazioni: Mostra solo gli aeroporti installati - verranno mostrati nella lista solo gli aeroporti presenti nello scenario installato sull'hard disk. Aggiorna la lista degli aeroporti installati - cerca gli scenari installati sull'hard disk e aggiorna la lista degli aeroporti. Funziona solo se l' opzione "Mostra solo gli aeroporti installati" è stata selezionata. Preferenze - apre la finestra delle preferenze. Strumenti: METAR - mostra il rapporto METAR per l'aeroporto selezionato (o quello più vicino). I rapporti vengono prelevati da http://weather.noaa.gov/. Aiuto: Aiuto - apre la finestra della guida di FGo!. Informazioni su FGo! - mostra informazioni sul programma. ------------------------------------------------------------------------------- FINESTRA DELLE OPZIONI A LINEA DI COMANDO Nella finestra più in basso è possibile aggiungere opzioni a linea di comando che verranno trasmesse a FlightGear. Alcune opzioni sono preimpostate, per altri esempi consultare la documentazione di FlightGear o visitare la pagina Wiki: http://wiki.flightgear.org/index.php/Command_Line_Options ------------------------------------------------------------------------------- SUGGERIMENTI * Se la sorgente dati degli aeroporti è impostata su "Scenario", può darsi che le informazioni sulla posizione dei parcheggi non siano disponibili fino al momento in cui lo scenario corrispondente viene installato. * È possibile iniziare un volo partendo da una portaerei. Nel pannello centrale cliccare sul codice ICAO dell'aeroporto (immediatamente sotto l'immagine del velivolo) e scegliere una delle navi disponibili. Il codice verrà sostituito dal nome della nave selezionata, inoltre il nome sarà evidenziato in blu per indicare che ci si trova in "modalità portaerei". Lo scenario corrispondente verrà automaticamente selezionato. Per scegliere di nuovo un aeroporto occorre cliccare sul nome della portaerei e selezionare il trattino (-). * Nella lista "Seleziona situazione" è possibile cliccare con il tasto destro del mouse su una qualsiasi per leggerne la descrizione (se è disponibile). * La risoluzione della finestra viene salvata premendo il pulsante "Salva ed esci". ------------------------------------------------------------------------------- DIFETTI CONOSCIUTI E LIMITI * FGo! non verifica se TerraSync è stato realmente chiuso prima di avviarne una nuova istanza. Se la casella "TerraSync" è stata deselezionata e selezionata nuovamente entro breve tempo, nel terminale può apparire il messaggio "error binding to port 5501". Ciò sta a indicare che la vecchia istanza di TerraSync non ha ancora finito di scaricare i dati. In tal caso attendere fino a quando i dati sono stati scaricati e selezionare di nuovo la casella "TerraSync". * I nomi di parcheggio molto lunghi fuoriescono dai limiti del pulsante. ------------------------------------------------------------------------------- Grazie a chi utilizza questo software, Robert 'erobo' Leda fgo/src/__init__.py0000644000175000017500000000355612426731462015365 0ustar robertrobert00000000000000#!/usr/bin/env python """FGo! - a simple GUI launcher for FlightGear Flight Simulator.""" import gettext from sys import argv from os import chdir from Tkinter import Tk def early_tk_init(): root = Tk() root.title('FGo!') return root # When importing 'config', the 'infowindow' module is itself imported, which # defines an InfoWindow class. This in turn requires the Tk() object (at class # definition time) because of the constructor's # 'font=tkFont.nametofont("TkTextFont")' optional argument. root = early_tk_init() from config import Config from gui.mainwindow import App from constants import LOCALE_DIR gettext.install('fgo', LOCALE_DIR, unicode=True) CLI_MESSAGE = """Usage: fgo This program does not use command line options. Edit fgo/data/config/presets file if you need to run FGo! with some pre-configuration.""" def run(working_dir, root=root): """Initialize application. The 'root=root' optional argument allows to keep a reference to the Tk() object without affecting the callers that provide only one argument. """ # Set current working directory. chdir(working_dir) # Initialize data object (passing 'root' allows things such as obtaining # the screen dpi in Config methods using root.winfo_fpixels('1i')). data = Config(root) promptToNotUseCli() # Initialize main window. app = App(root, data) # Set window resolution. window_geometry = data.window_geometry.get() if window_geometry: root.geometry(window_geometry) # Override window close button, so TerraSync # can be stopped before closing the program. root.protocol("WM_DELETE_WINDOW", app.quit) root.mainloop() del root, early_tk_init def promptToNotUseCli(): if len(argv) > 1: print _(CLI_MESSAGE) if __name__ == '__main__': from sys import path WORKING_DIR = path[0] run(WORKING_DIR) fgo/src/config.py0000644000175000017500000007020412372647240015065 0ustar robertrobert00000000000000"""This module process data from ~/.fgo folder.""" import os import gzip import gettext import codecs import traceback from Tkinter import IntVar, StringVar from tkMessageBox import showerror import tkFont from gui.infowindow import InfoWindow from constants import * class Config: """Read/write and store all data from config files.""" def __init__(self, master=None): self.master = master self.ai_path = '' # Path to FG_ROOT/AI directory. self.apt_path = '' # Path to FG_ROOT/Airports/apt.dat.gz file. self.default_aircraft_dir = '' # Path to FG_ROOT/Aircraft directory. self.metar_path = '' # Path to FG_ROOT/Airports/metar.dat.gz file. self.aircraft_dirs = [] # List of aircraft directories. self.aircraft_list = [] # List of aircraft names. self.aircraft_path = [] # List of paths to each aircraft. self.airport_icao = [] # List of ICAO codes for each airport. self.airport_name = [] # List of airport names. self.airport_rwy = [] # List of runways present in each airport. self.scenario_list = [] # List of selected scenarios. # List of all aircraft carriers found in AI scenario folder. # Each entry format is: # ["ship name", "parking position"... , "scenario name"] self.carrier_list = [] self.settings = [] # List of all settings read from config file. self.text = '' # String to be shown in command line options window. self.aircraft = StringVar() self.airport = StringVar() self.apt_data_source = IntVar() self.auto_update_apt = IntVar() self.carrier = StringVar() self.FG_aircraft = StringVar() self.FG_bin = StringVar() self.FG_root = StringVar() self.FG_scenery = StringVar() self.FG_working_dir = StringVar() self.filtredAptList = IntVar() self.language = StringVar() self.park = StringVar() self.rwy = StringVar() self.scenario = StringVar() self.TS = IntVar() self.TS_bin = StringVar() self.TS_port = StringVar() self.TS_scenery = StringVar() self.window_geometry = StringVar() self.baseFontSize = StringVar() self.TkDefaultFontSize = IntVar() self.keywords = {'--aircraft=': self.aircraft, '--airport=': self.airport, '--fg-root=': self.FG_root, '--fg-scenery=': self.FG_scenery, '--carrier=': self.carrier, '--parkpos=': self.park, '--runway=': self.rwy, 'AI_SCENARIOS=': self.scenario, 'APT_DATA_SOURCE=': self.apt_data_source, 'AUTO_UPDATE_APT=': self.auto_update_apt, 'FG_BIN=': self.FG_bin, 'FG_AIRCRAFT=': self.FG_aircraft, 'FG_WORKING_DIR=': self.FG_working_dir, 'FILTER_APT_LIST=': self.filtredAptList, 'LANG=': self.language, 'TERRASYNC=': self.TS, 'TERRASYNC_BIN=': self.TS_bin, 'TERRASYNC_PORT=': self.TS_port, 'TERRASYNC_SCENERY=': self.TS_scenery, 'WINDOW_GEOMETRY=': self.window_geometry, 'BASE_FONT_SIZE=': self.baseFontSize} self._createConfDirectory() self.update(first_run=True) self.setTkDefaultFontSize() self.setupFonts(init=True) def setTkDefaultFontSize(self): """Save unaltered TkDefaultFont size.""" size = tkFont.nametofont("TkDefaultFont").actual()["size"] self.TkDefaultFontSize.set(size) def setupFonts(self, init=False): """Setup the default fonts. When called with init=True, custom fonts are created and stored as attributes of self. Otherwise, they are simply configured. """ # According to , font # sizes are interpreted this way: # - for positive values, the unit is points; # - for negative values, the unit is pixels; # - 0 is a special value for "a platform-dependent default size". # # Apparently, Tkinter doesn't accept floats for the 'size' parameter of # .configure(), even when positive (tested with Python 2.7.3). baseSize = int(float(self.baseFontSize.get())) # Get the actual size when baseSize == 0, otherwise scaling won't work # since 0*factor == 0, regardless of the (finite) factor. if baseSize == 0: baseSize = self.TkDefaultFontSize.get() def scale(factor): return int(round(baseSize * factor)) def configFontSize(style, factor): font = tkFont.nametofont("Tk%sFont" % style) font.configure(size=scale(factor)) # Configure built-in fonts for style in ("Default", "Text", "Fixed", "Caption", "Tooltip"): configFontSize(style, 1) for style, factor in (("Menu", 20 / 18.), ("Heading", 20 / 18.), ("SmallCaption", 16 / 18.), ("Icon", 14 / 18.)): configFontSize(style, factor) # Create or configure custom fonts, depending on 'init' aboutTitleFontSize = scale(42 / 18.) if init: self.aboutTitleFont = tkFont.Font( family="Helvetica", weight="bold", size=aboutTitleFontSize) else: self.aboutTitleFont.configure(size=aboutTitleFontSize) def makeInstalledAptList(self): airports = self._findInstalledApt() s = '\n'.join(airports) fout = open(INSTALLED_APT, 'w') fout.writelines(s) fout.close() def readCoord(self): """Read coordinates list (makes new one if non exists). Return dictionary of ICAO codes and its coordinates. """ try: # Make new file. if not os.path.exists(APT): self._makeApt() res = {} fin = open(APT) for line in fin: line = line.strip().split('=') icao = line[0] # Read coordinates. coords_line = line[-1].split(';') coords = (float(coords_line[0]), float(coords_line[1])) res[icao] = coords fin.close() return res except IOError: return None def readMetarDat(self): """Fetch METAR station list from metar.dat.gz file""" try: fin = gzip.open(self.metar_path) res = [] for line in fin: if not line.startswith('#'): line = line.strip() res.append(line) fin.close() return res except IOError: return ['IOError'] def rebuildApt(self): """Rebuild apt file.""" self._makeApt() def update(self, path=None, first_run=False): """Read config file and update variables. path is a path to different than default config file first_run specifies if TS_checkbutton and filtered airports list will be updated. """ del self.settings del self.text del self.aircraft_dirs del self.default_aircraft_dir del self.apt_path del self.ai_path del self.metar_path del self.aircraft_list del self.aircraft_path del self.airport_icao del self.airport_name del self.airport_rwy del self.scenario_list del self.carrier_list self.aircraft.set(DEFAULT_AIRCRAFT) self.airport.set(DEFAULT_AIRPORT) self.apt_data_source.set(0) self.auto_update_apt.set(0) self.carrier.set('None') self.FG_aircraft.set('') self.FG_bin.set('') self.FG_root.set('') self.FG_scenery.set('') self.FG_working_dir.set('') self.language.set('') self.baseFontSize.set(DEFAULT_BASE_FONT_SIZE) self.park.set('None') self.rwy.set('Default') self.scenario.set('') self.TS_bin.set('') self.TS_port.set('') self.TS_scenery.set('') if first_run: self.TS.set(0) self.filtredAptList.set(0) self.settings = self._read(path) self.text = self._makeText() for line in self.settings: cut = line.find('=') + 1 if cut: name = line[:cut] value = line[cut:] if value: if name in self.keywords: if name == 'FILTER_APT_LIST=' and not first_run: pass elif name == 'TERRASYNC=' and not first_run: pass else: var = self.keywords[name] var.set(value) self._setLanguage(self.language.get()) self.default_aircraft_dir = os.path.join(self.FG_root.get(), DEFAULT_AIRCRAFT_DIR) self.aircraft_dirs = self._readAircraftDirs() self.aircraft_dirs = [self.default_aircraft_dir] + self.aircraft_dirs self.apt_path = os.path.join(self.FG_root.get(), APT_DAT) self.ai_path = os.path.join(self.FG_root.get(), AI_DIR) self.metar_path = os.path.join(self.FG_root.get(), METAR_DAT) self.aircraft_list, self.aircraft_path = self._readAircraft() self.scenario_list, self.carrier_list = self._readScenarios() self.updateAptLists() def updateAptLists(self): if self.auto_update_apt.get() and os.path.exists(self.apt_path): self._autoUpdateApt() self.airport_icao, self.airport_name, self.airport_rwy = \ self._readApt() def write(self, text, path=None): """Write options to a file. text argument should be a content of text window path is a path to different than default config file. """ if not path: path = CONFIG options = [] keys = self.keywords.keys() keys.sort() # Text processed_text = [] text = text.split('\n') for line in text: line = line.strip() cut = line.find('=') + 1 if cut: name = line[:cut] value = line[cut:] else: name = '' if name in keys: self.keywords[name].set(value) else: if line != CUT_LINE: processed_text.append(line) processed_text = '\n'.join(processed_text) for k in keys: v = self.keywords[k] if v.get() not in ('Default', 'None'): options.append(k + unicode(v.get())) s = '\n'.join(options) config_out = codecs.open(path, 'w', 'utf-8') config_out.write(s + '\n' + CUT_LINE + '\n') config_out.write(processed_text) config_out.close() def _findInstalledApt(self): """Walk thru all scenery and find installed airports. Take geographic coordinates from directories names and compare them with airports coordinates in apt file. The result is a list of matching airports. """ coord_dict = {} sceneries = self.FG_scenery.get().split(':') for scenery in sceneries: path = os.path.join(scenery, 'Terrain') if os.path.exists(path): for dir in os.listdir(path): p = os.path.join(path, dir) for coords in os.listdir(p): converted = self._stringToCoordinates(coords) coord_dict[converted] = None apt_coords = self.readCoord() coords = coord_dict.keys() res = {} for k, v in apt_coords.items(): for c in coords: if v[0] > c[0][0] and v[0] < c[0][1] and \ v[1] > c[1][0] and v[1] < c[1][1]: res[k] = None res = res.keys() res.sort() return res def _calculateRange(self, coordinates): c = coordinates if c.startswith('s') or c.startswith('w'): c = int(c[1:]) * (-1) return c, c + 1 else: c = int(c[1:]) return c, c + 1 def _createConfDirectory(self): """Create config directory if non exists.""" if not os.path.exists(USER_DATA_DIR): os.mkdir(USER_DATA_DIR) def _makeApt(self, head=None): """Build apt database from apt.dat.gz""" if self.FG_root.get(): _ProcessApt(self.master, self.apt_path, head) def _makeText(self): """Filter config file entries to be shown in text window.""" L = [] for line in self.settings: cut = line.find('=') + 1 if cut: name = line[:cut] value = line[cut:] if name not in self.keywords: L.append(name + value) else: L.append(line) return '\n'.join(L) def _read(self, path=None): """Read config file""" res = [] if not path: path = CONFIG # Use default config if no regular config exists. if not os.path.exists(path): # Find currently used language. try: lang_code = gettext.translation( MESSAGES, LOCALE_DIR).info()['language'] except IOError: lang_code = 'en' path = os.path.join(DEFAULT_CONFIG_DIR, 'config_' + lang_code) if not os.path.isfile(path): lang_code = 'en' path = os.path.join(DEFAULT_CONFIG_DIR, 'config_' + lang_code) # Load presets if exists. try: presets = codecs.open(PRESETS, encoding='utf-8') for line in presets: line = line.strip() if not line.startswith('#'): res.append(line) presets.close() except IOError: pass try: config_in = codecs.open(path, encoding='utf-8') for line in config_in: line = line.strip() if line != CUT_LINE: res.append(line) config_in.close() return res except IOError: return [''] def _readAircraft(self): """Walk through Aircraft directories and return two sorted lists: list of aircraft names and list of paths to them.""" n, p = [], [] for dir_ in self.aircraft_dirs: if os.path.isdir(dir_): self._readAircraftData(dir_, dir_, n) for d in os.listdir(dir_): self._readAircraftData(dir_, d, n) n.sort() for i in range(len(n)): p.append(n[i][2]) n[i] = n[i][1] return n, p def _readAircraftData(self, dir_, d, n): path = os.path.join(dir_, d) if os.path.isdir(path): try: for f in os.listdir(path): self._appendAircraft(f, n, path) except OSError: pass def _appendAircraft(self, f, n, path): if f.endswith('-set.xml'): # Dirty and ugly hack to prevent carrier-set.xml in # seahawk directory to be attached to the aircraft # list. if (not path.startswith('seahawk') and f != 'carrier-set.xml'): name = f[:-8] n.append([name.lower(), name, path]) def _readAircraftDirs(self): return self.FG_aircraft.get().split(':') def _readApt(self): """Read apt list (makes new one if non exists). Return tuple of three lists: airports ICAO codes, airports names, airports runways Take a note that those lists are synchronized - each list holds information about the same airport at given index. """ try: # Make new file. if not os.path.exists(APT): self._makeApt() icao, name, rwy = [], [], [] fin = open(APT) if self.filtredAptList.get(): installed_apt = self._readInstalledAptList() for line in fin: line = line.strip().split('=') if line[0] in installed_apt: installed_apt.remove(line[0]) icao.append(line[0]) name.append(line[1]) rwy_list = [] for i in line[2:-1]: rwy_list.append(i) rwy_list.sort() rwy.append(rwy_list) else: for line in fin: line = line.strip().split('=') icao.append(line[0]) name.append(line[1]) rwy_list = [] for i in line[2:-1]: rwy_list.append(i) rwy.append(rwy_list) fin.close() return icao, name, rwy except IOError: return [], [], [] def _readInstalledAptList(self): """Read locally installed airport list. Make new one if non exists. """ if not os.path.exists(INSTALLED_APT): self.makeInstalledAptList() res = [] fin = open(INSTALLED_APT) for line in fin: icao = line.strip() res.append(icao) fin.close() return res def _readScenarios(self): """Walk through AI scenarios and read carrier data. Return list of all scenarios and carrier data list """ try: carriers = [] scenarios = [] L = [] is_carrier = False for f in os.listdir(self.ai_path): path = os.path.join(self.ai_path, f) if os.path.isfile(path): if f.lower().endswith('xml'): scenarios.append(f[:-4]) fin = open(path) for line in fin: line = line.strip() if '' in line.lower(): typ = line[6:-7] if typ.lower() == 'carrier': is_carrier = True L = [] if '' in line.lower(): if L: L.append(f[:-4]) carriers.append(L) L = [] is_carrier = False if is_carrier: if '' in line.lower(): L.append(line[6:-7]) fin.close() carriers.sort() scenarios.sort() return scenarios, carriers except OSError: return [], [] def _setLanguage(self, lang): # Initialize provided language... try: L = gettext.translation(MESSAGES, LOCALE_DIR, languages=[lang]) L.install() # ...or fallback to system default. except: gettext.install(MESSAGES, LOCALE_DIR) def _stringToCoordinates(self, coordinates): """Convert geo coordinates to decimal format.""" lat = coordinates[4:] lon = coordinates[:4] lat_range = self._calculateRange(lat) lon_range = self._calculateRange(lon) return lat_range, lon_range def _autoUpdateApt(self): if not self.auto_update_apt.get() or not os.path.exists(self.apt_path): return old_timestamp = self._readAptTimestamp() self._updateApt(old_timestamp) def _readAptTimestamp(self): if not os.path.exists(APT_TIMESTAMP): self._writeAptTimestamp('') with open(APT_TIMESTAMP) as timestamp: old_modtime = timestamp.read() timestamp.close() return old_modtime def _writeAptTimestamp(self, s): with open(APT_TIMESTAMP, 'w') as timestamp: timestamp.write(s) timestamp.close() def _getAptModTime(self): return str(os.path.getmtime(self.apt_path)) def _updateApt(self, old_timestamp): if old_timestamp != self._getAptModTime(): self._makeApt(head=_('Modification of apt.dat.gz detected.')) self._writeAptTimestamp(self._getAptModTime()) class _ProcessApt: """Build apt database from apt.dat.gz""" def __init__(self, master, apt_path, head=None): self.master = master self.apt_path = apt_path self.data = [] self.main(head) def main(self, head): if os.path.exists(self.apt_path): self.make_window(head) try: self.makeApt() except AptdatHeaderError: self.show_aptdat_header_error() self.close_window() return except: print traceback.format_exc() self.show_aptdat_general_error() self.close_window() return self.close_window() else: self.show_no_aptdat_error() def make_window(self, head=None): message = _('Generating airport database,\nthis can take a while...') if head: message = '\n'.join((head, message)) self.window = InfoWindow(self.master, message) self.window.update() def makeApt(self): self.reset_variables() version = self.get_apt_version_number() self.process_atp(version) self.data.sort() self.save_atp_data() def reset_variables(self): self.entry = '' self.lat, self.lon = 0.0, 0.0 self.runway_count = 0 def get_apt_version_number(self): """Return version number of apt.dat.gz file. It can be found at the beginning of a second line of the header. """ with gzip.open(self.apt_path) as fin: origin, number, version = self.read_header(fin) number = self.get_version_number(origin, number, version) return number def read_header(self, fin): data = fin.read(24).split() try: origin, number, version = data[0], data[1], data[2] return origin, number, version except: raise AptdatHeaderError def get_version_number(self, origin, number, version): if ((origin == 'A' or origin == 'I') and number.isdigit() and version == 'Version'): return number else: raise AptdatHeaderError def process_atp(self, version): with gzip.open(self.apt_path) as fin: for line in fin: # apt.dat is apparently using iso-8859-1 coding, # so we need to decode it to utf-8. line = line.decode('iso-8859-1').encode('utf-8') e = line.strip().split() self.process_airport_data(version, e) self.finalize_processing_airport(line) def process_airport_data(self, version, e): if int(version) < 850: self.process_data_v810(e) else: self.process_data_v850(e) def process_data_v810(self, e): if e and e[0] in ('1', '16', '17'): self.get_atp_name(e) # Find runways. elif self.entry and e and e[0] == '10': if e[3] != 'xxx': rwy = e[3] if rwy.endswith('x'): rwy = rwy[:-1] lat, lon = e[1], e[2] self.set_rwy_data(rwy, lat, lon, addotherend=True) self.runway_count += 1 def set_rwy_data(self, rwy, lat, lon, addotherend=False): """Set runway data. If addotherend is set to True, it computes and adds other end of given rwy. It is needed for apt.dat v 810 and earlier data format, where only one runway end is provided. """ self.entry += ('=' + rwy) if addotherend: self.add_other_end(rwy) self.set_position_data(lat, lon) def set_position_data(self, lat, lon): self.lat += float(lat) self.lon += float(lon) def add_other_end(self, rwy): rwy_number = self.compute_other_end(rwy) if rwy_number: self.entry += ('=' + rwy_number) def compute_other_end(self, rwy): if not rwy.startswith('H'): prefix = self.compute_heading(rwy) suffix = self.change_left_right(rwy) return prefix + suffix def compute_heading(self, rwy): number = self.get_heading(rwy) if number <= 36 and number > 18: prefix = str(number - 18) elif number > 0 and number <= 18: prefix = str(number + 18) return prefix def get_heading(self, rwy): while not rwy.isdigit(): rwy = rwy[:-1] return int(rwy) def change_left_right(self, rwy): suffix = '' if rwy.endswith('L'): suffix = 'R' elif rwy.endswith('R'): suffix = 'L' elif rwy.endswith('C'): suffix = 'C' return suffix def process_data_v850(self, e): if e and e[0] in ('1', '16', '17'): self.get_atp_name(e) # Find runways. elif self.entry and e and e[0] == '100': self.set_first_rwy_v850(e) self.set_second_rwy_v850(e) self.runway_count += 2 # Find water runways. elif self.entry and e and e[0] == '101': self.set_first_water_rwy_v850(e) self.set_second_water_rwy_v850(e) self.runway_count += 2 # Find helipads. elif self.entry and e and e[0] == '102': self.set_helipad_v850(e) self.runway_count += 1 def set_first_rwy_v850(self, e): self.set_rwy_data(e[8], e[9], e[10]) def set_second_rwy_v850(self, e): self.set_rwy_data(e[17], e[18], e[19]) def set_first_water_rwy_v850(self, e): self.set_rwy_data(e[3], e[4], e[5]) def set_second_water_rwy_v850(self, e): self.set_rwy_data(e[6], e[7], e[8]) def set_helipad_v850(self, e): self.set_rwy_data(e[1], e[2], e[3]) def get_atp_name(self, e): """Get ICAO and airport name.""" name = [e[4], ' '.join(e[5:])] name = '='.join(name) self.entry = name def finalize_processing_airport(self, line): if self.entry and (line in ['\n', '\r\n'] or line.startswith('99')): self.averaged_coordinates() self.set_coordinates() self.reset_variables() def averaged_coordinates(self): if self.runway_count: self.lat /= self.runway_count self.lon /= self.runway_count def set_coordinates(self): coords = ';'.join([str(self.lat), str(self.lon)]) self.entry = '='.join([self.entry, coords]) self.data.append(self.entry) def save_atp_data(self): with open(APT, 'w') as fin: for i in self.data: fin.write(str(i) + '\n') def show_aptdat_header_error(self): message = _('Cannot read version number of apt.dat database.\n\n' 'FGo! expects a proper header information at the ' 'beginning of apt.dat. For details about data format see ' 'http://data.x-plane.com/designers.html#Formats') showerror(_('Error'), message) def show_aptdat_general_error(self): message = _('Cannot read apt.dat database.') showerror(_('Error'), message) def show_no_aptdat_error(self): message = _('Cannot find apt.dat database.') showerror(_('Error'), message) def close_window(self): self.window.destroy() class AptdatHeaderError(Exception): pass fgo/src/constants.py0000644000175000017500000000623312436636501015634 0ustar robertrobert00000000000000"""Here are defined all global constants.""" from os.path import expanduser, join, pardir # FGo! related constants. NAME = 'FGo! 1.5.5' USER_AGENT = 'FGo!/1.5.5 (+http://sites.google.com/site/erobosprojects/flightgear/add-ons/fgo)' COPYRIGHT = "Copyright 2009-2014 by\nRobert 'erobo' Leda " AUTHORS = 'Robert Leda\nFlorent Rougon' LICENSE = \ """This program is free software. It comes without any warranty, to the extent permitted by applicable law. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See http://sam.zoy.org/wtfpl/COPYING for more details. """ # User's home directory. HOME_DIR = expanduser('~') # Default directory for user data files. USER_DATA_DIR = join(HOME_DIR, '.fgo') # Path to airport data file. APT = join(USER_DATA_DIR, 'apt') # Path to locally installed airport list. INSTALLED_APT = join(USER_DATA_DIR, 'apt_installed') # Path to config file. CONFIG = join(USER_DATA_DIR, 'config') # Path to apt.dat.gz timestamp file. APT_TIMESTAMP = join(USER_DATA_DIR, 'timestamp') # Path to default config directory. DATA_DIR = join(pardir, 'data') DEFAULT_CONFIG_DIR = join(DATA_DIR, 'config') # Path to config file with predefined settings. PRESETS = join(DEFAULT_CONFIG_DIR, 'presets') # Path to help directory. HELP_DIR = join(DATA_DIR, 'help') # Name of directory where localization files are stored. LOCALE_DIR = join(DATA_DIR, 'locale') # Name of localization file. MESSAGES = 'messages' # Path to substitutionary thumbnail. NO_PIL_PIC = join(DATA_DIR, 'pics', 'thumbnail.ppm') # Path to substitutionary thumbnail. NO_THUMBNAIL_PIC = join(DATA_DIR, 'pics', 'thumbnail.jpg') # Line used in config file. CUT_LINE = ' INTERNAL OPTIONS ABOVE. EDIT CAREFULLY! '.center(80, 'x') # Default aircraft. DEFAULT_AIRCRAFT = 'c172p' # Default airport. DEFAULT_AIRPORT = 'KSFO' # Default port for TerraSync. DEFAULT_PORT = '5501' # Tooltip delay in milliseconds. TOOLTIP_DELAY = '600' # Default value for the base font size in points. Should be 0, or in the range # from MIN_BASE_FONT_SIZE to MAX_BASE_FONT_SIZE. 0 is special-cased by Tk and # corresponds to a platform-dependent default size. DEFAULT_BASE_FONT_SIZE = '0' # The minimum user-selectable value. MIN_BASE_FONT_SIZE = '6' # The maximum user-selectable value. MAX_BASE_FONT_SIZE = '36' # Custom colors. # Color to highlight background for carrier name in the main window. CARRIER_COL = '#98afd9' # Color to highlight comments in text window. COMMENT_COL = '#0014a7' # Color to apply to rwy button when inactive. GRAYED_OUT_COL = '#b2b2b2' # Background color for various messages. MESSAGE_BG_COL = '#fffeb2' # Background color for various text windows. TEXT_BG_COL = '#ffffff' # FG related constants. # FG_DATA/AI directory name. AI_DIR = 'AI' # FG_DATA/Aircraft directory name. DEFAULT_AIRCRAFT_DIR = 'Aircraft' # FG_DATA(or FG_SCENERY)/Airports directory name. DEFAULT_AIRPORTS_DIR = 'Airports' # FG_DATA/Airports/apt.dat.gz file path. APT_DAT = join('Airports', 'apt.dat.gz') # FG_DATA/Airports/apt.dat.gz file path. METAR_DAT = join('Airports', 'metar.dat.gz') __all__ = tuple(i for i in locals().keys() if i.isupper()) fgo/src/gui/infowindow.py0000644000175000017500000000173212315554666016575 0ustar robertrobert00000000000000"""Generic message window with close button deactivated""" from string import punctuation from Tkinter import Toplevel, Label import tkFont class InfoWindow(Toplevel): def __init__(self, master=None, text=None, font=tkFont.nametofont("TkHeadingFont"), cnf={}, **kw): Toplevel.__init__(self, master, cnf, **kw) self._window(master, text, font) def _window(self, master, text, font): self.title(self.getFirstLine(text)) self.protocol("WM_DELETE_WINDOW", self._doNotQuit) self.resizable(width=False, height=False) self.grab_set() # Focus input on that window. self.transient(master) self.label = Label(self, borderwidth=20, text=text, font=font) self.label.pack() def getFirstLine(self, s): first_line = s.split('\n')[0] return first_line.rstrip(punctuation) def _doNotQuit(self): """Dumb method to override window's close button.""" return fgo/src/gui/metar.py0000644000175000017500000001437312312146614015512 0ustar robertrobert00000000000000"""Simple widget to display METAR reports from weather.noaa.gov/.""" from math import sqrt from socket import setdefaulttimeout, timeout from urllib2 import Request, URLError, build_opener, HTTPHandler from thread import start_new_thread from Tkinter import * from ..constants import USER_AGENT setdefaulttimeout(5.0) DEBUG_LEVEL = 0 class Metar: def __init__(self, master, config, background): self.master = master self.background = background self.icao = config.airport self.apt_path = config.apt_path self.metar_path = config.metar_path self.decoded = IntVar() self.nearest_station = StringVar() self.report = StringVar() self.decoded.set(0) self.nearest_station.set('') self.report.set('') self.metar_list = config.readMetarDat() self.apt_dict = config.readCoord() #------- Main Window ---------------------------------------------------------- self.top = Toplevel(self.master, borderwidth=4) self.top.transient(self.master) self.top.resizable(width=False, height=False) # Override window close button. self.top.protocol("WM_DELETE_WINDOW", self.quit) self.top.title('METAR') self.top.bind('', self.quit) self.frame1 = Frame(self.top) self.frame1.pack(side='top', fill='x') #------ Decoded check button -------------------------------------------------- self.decoded_cb = Checkbutton(self.frame1, text=_('Decoded'), variable=self.decoded) self.decoded_cb.pack(side='left') self.frame2 = Frame(self.top, borderwidth=2, relief='sunken') self.frame2.pack(side='top') #------ Report window --------------------------------------------------------- self.text = Label(self.frame2, width=0, height=0, bg=self.background, textvariable=self.report) self.text.pack(side='top') self.text.bind('', self.fetch) #------------------------------------------------------------------------------ self._welcomeMessage() def fetch(self, event=None): """Fetch METAR report.""" self.text.unbind('') self.report.set(_('Fetching report...')) # Wait until text is updated. self.master.update() start_new_thread(self._fetch, ()) def quit(self, event=None): """Clean up data and destroy this window.""" del self.metar_list del self.apt_path del self.metar_path del self.icao del self.master del self.background del self.apt_dict self.top.destroy() def _bindButton(self): try: self.text.bind('', self.fetch) except TclError: return def _compare_pos(self, a, b): return sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) def _fetch(self): """Fetch METAR report.""" icao = self._getIcao() if icao == 'None': return self.nearest_station.set('') if self._isOnMetarList(icao): decoded = self._getDecoded() url = \ ('http://weather.noaa.gov/pub/data/observations/metar/%s/%s.TXT' % (decoded, icao)) try: request = Request(url) request.add_header('User-Agent', USER_AGENT) opener = build_opener(HTTPHandler(debuglevel=DEBUG_LEVEL)) report = opener.open(request).read() report = report.strip() # FIXME # The timeout exception seems not to be caught on my machine (with # Debian Squeeze and Python 2.5 or 2.6) if internet connection is # disabled. Have no idea what is wrong. except timeout: report = _('Unable to download data.') except URLError: report = _('Unable to download data.') self.report.set(report) self._setLabelSize() else: self.nearest_station.set(self._nearestMetar(icao)) self.fetch() # Bind button to text widget after some delay to avoid double clicking. self.master.after(1000, self._bindButton) def _getIcao(self): if self.nearest_station.get() and \ self.nearest_station.get() == self._nearestMetar(self.icao.get()): return self.nearest_station.get() else: return self.icao.get() def _getDecoded(self): if self.decoded.get(): return 'decoded' else: return 'stations' def _isOnMetarList(self, icao): """Return True if selected airport is on METAR station list.""" if icao in self.metar_list: return True def _nearestMetar(self, icao): """Find nearest METAR station""" nearest_metar = '' nearest_dist = 999 try: airport_pos = self.apt_dict[icao] except KeyError: return '' for icao in self.metar_list: try: metar_pos = self.apt_dict[icao] distance = self._compare_pos(airport_pos, metar_pos) if distance < nearest_dist: nearest_metar = icao nearest_dist = distance except KeyError: pass return nearest_metar def _setLabelSize(self): """Adjust label dimensions according to text size.""" report = self.report.get() report = report.split('\n') width = -1 for line in report: if len(line) > width: width = len(line) height = len(report) + 2 try: self.text.configure(width=width, height=height) except TclError: pass def _welcomeMessage(self): """Show message at widget's initialization""" # Disable widget in case of IOError. if self.metar_list[0] == 'IOError': self.text.unbind('') message = ' ' * 30 else: message = _('Click here to download the METAR report\n' 'for selected (or nearest) airport.') self.report.set(message) self._setLabelSize() fgo/src/gui/mainwindow.py0000644000175000017500000012065712436610724016566 0ustar robertrobert00000000000000"""Application main window.""" import os import subprocess import codecs import socket from gettext import translation from Tkinter import * import tkFileDialog as fd from ScrolledText import ScrolledText from xml.etree.ElementTree import ElementTree from tkMessageBox import showerror try: from PIL import Image, ImageTk PIL = True except ImportError: PIL = False print ('[FGo! Warning] PIL library not found. Aircraft thumbnails ' 'will not be displayed.') from metar import Metar from configwindow import ConfigWindow from fglauncher import FGLauncher from ..constants import * class App: def __init__(self, master, config): self.master = master self.config = config self.translatedPark = StringVar() self.translatedRwy = StringVar() self.translatedPark.set(_('None')) self.translatedRwy.set(_('Default')) #------ Menu ------------------------------------------------------------------ self.menubar = Menu(self.master) self.filemenu = Menu(self.menubar, tearoff=0) self.filemenu.add_command(label=_('Load'), command=self.configLoad) self.filemenu.add_command(label=_('Save as...'), command=self.configSave) self.filemenu.add_separator() self.filemenu.add_command(label=_('Save & Quit'), command=self.saveAndQuit) self.filemenu.add_command(label=_('Quit'), command=self.quit) self.menubar.add_cascade(label=_('File'), menu=self.filemenu) self.settmenu = Menu(self.menubar, tearoff=0) self.settmenu.add_checkbutton(label=_('Show installed airports only'), variable=self.config.filtredAptList, command=self.filterAirports) self.settmenu.add_command(label=_('Update list of installed airports'), command=self.updateInstalledAptList) self.settmenu.add_separator() self.settmenu.add_command(label=_('Preferences'), command=self.showConfigWindow) self.menubar.add_cascade(label=_('Settings'), menu=self.settmenu) self.toolsmenu = Menu(self.menubar, tearoff=0) self.toolsmenu.add_command(label='METAR', command=self.showMETARWindow) self.menubar.add_cascade(label=_('Tools'), menu=self.toolsmenu) self.helpmenu = Menu(self.menubar, tearoff=0) self.helpmenu.add_command(label=_('Help'), command=self.showHelpWindow) self.helpmenu.add_separator() self.helpmenu.add_command(label=_('About'), command=self.about) self.menubar.add_cascade(label=_('Help'), menu=self.helpmenu) self.master.config(menu=self.menubar) self.frame = Frame(self.master) self.frame.pack(side='top', fill='both', expand=True) self.frame0 = Frame(self.frame, borderwidth=4) self.frame0.pack(side='top', fill='x') #------ Aircraft list --------------------------------------------------------- self.frame1 = Frame(self.frame0, borderwidth=8) self.frame1.pack(side='left', fill='both', expand=True) self.frame11 = Frame(self.frame1, borderwidth=1) self.frame11.pack(side='top', fill='both', expand=True) self.scrollbar = Scrollbar(self.frame11, orient='vertical') self.aircraftList = Listbox(self.frame11, bg=TEXT_BG_COL, exportselection=0, yscrollcommand=self.scrollbar.set, height=14) self.scrollbar.config(command=self.aircraftList.yview, takefocus=0) self.aircraftList.pack(side='left', fill='both', expand=True) self.scrollbar.pack(side='left', fill='y') self.aircraftList.see(self.getIndex('a')) self.aircraftList.bind('', self.focusAircraftList) self.frame12 = Frame(self.frame1, borderwidth=1) self.frame12.pack(side='top', fill='x') self.aircraftSearch = Entry(self.frame12, bg=TEXT_BG_COL) self.aircraftSearch.pack(side='left', fill='x', expand=True) self.aircraftSearch.bind('', self.aircraftSearchStart) self.aircraftSearch.bind('', self.aircraftSearchStop) self.aircraftSearchButton = Button(self.frame12, text=_('Clear'), command=self.aircraftSearchClear) self.aircraftSearchButton.pack(side='left') #------ Middle panel ---------------------------------------------------------- self.frame2 = Frame(self.frame0, borderwidth=1, relief='sunken') self.frame2.pack(side='left', fill='both') # Aircraft self.frame21 = Frame(self.frame2, borderwidth=4) self.frame21.pack(side='top', expand=True) self.aircraftLabel = Label(self.frame21, textvariable=self.config.aircraft) self.aircraftLabel.pack(side='top') self.thumbnail = Label(self.frame21, width=171, height=128) self.thumbnail.pack(side='top', fill='y') self.updateImage() # Airport, rwy and parking self.frame22 = Frame(self.frame2, borderwidth=4) self.frame22.pack(side='top', fill='x') # First column self.frame221 = Frame(self.frame22, borderwidth=4) self.frame221.pack(side='left', fill='x') self.airport_label = Label(self.frame221, text=_('Airport:')) self.airport_label.pack(side='top') self.rwy_label = Label(self.frame221, text=_('Rwy:')) self.rwy_label.pack(side='top') self.park_label = Label(self.frame221, text=_('Parking:')) self.park_label.pack(side='top') # Second column self.frame222 = Frame(self.frame22, borderwidth=4) self.frame222.pack(side='left', fill='x') self.airportLabel = Label(self.frame222, width=12, textvariable=self.config.airport, relief='groove', borderwidth=2) self.airportLabel.pack(side='top') self.airportLabel.bind('', self.popupCarrier) self.rwyLabel = Label(self.frame222, width=12, textvariable=self.translatedRwy, relief='groove', borderwidth=2) self.rwyLabel.pack(side='top') self.rwyLabel.bind('', self.popupRwy) self.parkLabel = Label(self.frame222, width=12, textvariable=self.translatedPark, relief='groove', borderwidth=2) self.parkLabel.pack(side='top') self.parkLabel.bind('', self.popupPark) # AI Scenarios self.frame23 = Frame(self.frame2) self.frame23.pack(side='top', fill='both') self.scenarios = Label(self.frame23, text=_('Select Scenario'), relief='groove', padx=6, pady=6) self.scenarios.pack(side='left', fill='both', expand=True) self.scenarios.bind('', self.popupScenarios) #------ Airport list ---------------------------------------------------------- self.frame3 = Frame(self.frame0, borderwidth=8) self.frame3.pack(side='left', fill='both', expand=True) self.frame31 = Frame(self.frame3, borderwidth=1) self.frame31.pack(side='top', fill='both', expand=True) self.sAirports = Scrollbar(self.frame31, orient='vertical') self.airportList = Listbox(self.frame31, bg=TEXT_BG_COL, exportselection=0, yscrollcommand=self.sAirports.set, height=14) self.sAirports.config(command=self.airportList.yview, takefocus=0) self.airportList.pack(side='left', fill='both', expand=True) self.sAirports.pack(side='left', fill='y') self.airportList.see(self.getIndex('p')) self.airportList.bind('', self.focusAirportList) self.frame32 = Frame(self.frame3, borderwidth=1) self.frame32.pack(side='top', fill='x') self.airportSearch = Entry(self.frame32, bg=TEXT_BG_COL) self.airportSearch.pack(side='left', fill='x', expand=True) self.airportSearch.bind('', self.airportSearchStart) self.airportSearch.bind('', self.airportSearchStop) self.airportSearchButton = Button(self.frame32, text=_('Clear'), command=self.airportSearchClear) self.airportSearchButton.pack(side='left') #------ Buttons --------------------------------------------------------------- self.frame4 = Frame(self.frame, borderwidth=4) self.frame4.pack(side='top', fill='x') self.frame41 = Frame(self.frame4, borderwidth=4) self.frame41.pack(side='left', fill='x') # TerraSync self.ts = Checkbutton(self.frame41, text="TerraSync", variable=self.config.TS, command=self.runTS) self.ts.pack(side='left') self.ts_prefetch = Button(self.frame41, text=_('Scenery Prefetch'), command=self.prefetchScenery) self.ts_prefetch.pack(side='left') self.frame42 = Frame(self.frame4, borderwidth=4) self.frame42.pack(side='right') # Buttons self.sq_button = Button(self.frame42, text=_('Save & Quit'), command=self.saveAndQuit) self.sq_button.pack(side='left') self.reset_button = Button(self.frame42, text=_('Reset'), width=10, command=self.reset) self.reset_button.pack(side='left') self.run_button = Button(self.frame42, text=_('Run FG'), width=10, command=self.runFG) self.run_button.pack(side='left') #------ Text window ----------------------------------------------------------- self.frame5 = Frame(self.frame) self.frame5.pack(side='top', fill='both', expand=True) self.frame51 = Frame(self.frame5) self.frame51.pack(side='left', fill='both', expand=True) self.text = ScrolledText(self.frame51, bg=TEXT_BG_COL, wrap='none') self.text.pack(side='left', fill='both', expand=True) #------------------------------------------------------------------------------ self.default_fg = self.rwyLabel.cget('fg') self.default_bg = self.master.cget('bg') self.scenarioListOpen = False self.mainLoopIsRuning = False self.aircraftSearchIsRunning = False self.airportSearchIsRunning = False self.currentCarrier = [] self.old_rwy = self.config.rwy.get() self.old_park = self.config.park.get() self.old_aircraft_search = '' self.old_airport_search = '' self.reset(first_run=True) self.startLoops() self.runTS() def about(self): """Create 'About' window""" try: self.aboutWindow.destroy() except AttributeError: pass if _('Translation:') == 'Translation:': translator = '' else: translator = '\n\n' + _('Translation:') authors = _('Authors:') about_text = '{0}\n\n{1}\n{2}{3}'.format(COPYRIGHT, authors, AUTHORS, translator) self.aboutWindow = Toplevel(borderwidth=4) self.aboutWindow.title(_('About')) self.aboutWindow.resizable(width=False, height=False) self.aboutWindow.transient(self.master) self.aboutWindow.bind('', self._destroyAboutWindow) self.aboutTitle = Label(self.aboutWindow, font=self.config.aboutTitleFont, text=NAME) self.aboutTitle.pack() self.aboutFrame1 = Frame(self.aboutWindow, borderwidth=1, relief='sunken', padx=8, pady=12) self.aboutFrame1.pack(fill='x', expand=True) self.aboutText = Label(self.aboutFrame1, text=about_text) self.aboutText.pack() self.aboutFrame2 = Frame(self.aboutWindow, borderwidth=12) self.aboutFrame2.pack() self.aboutLicense = Button(self.aboutFrame2, text=_('License'), command=self.aboutShowLicense) self.aboutLicense.pack(side='left') self.aboutClose = Button(self.aboutFrame2, text=_('Close'), command=self._destroyAboutWindow) self.aboutClose.pack(side='left') def _destroyAboutWindow(self, event=None): self.aboutWindow.destroy() def aboutShowLicense(self): self.aboutText.configure(text=LICENSE) self.aboutTitle.destroy() self.aboutLicense.destroy() def aircraftSearchClear(self): self.aircraftSearch.delete('0', 'end') self.old_aircraft_search = '' self.searchAircrafts() def aircraftSearchStart(self, event=None): self.aircraftSearchIsRunning = True self.aircraftSearchUpdate() def aircraftSearchStop(self, event=None): self.aircraftSearchIsRunning = False def aircraftSearchUpdate(self): if self.aircraftSearchIsRunning: if self.old_aircraft_search != self.aircraftSearch.get(): self.searchAircrafts() self.old_aircraft_search = self.aircraftSearch.get() self.master.after(100, self.aircraftSearchUpdate) else: self.old_aircraft_search = '' return def airportSearchClear(self): self.airportSearch.delete('0', 'end') self.old_airport_search = '' self.searchAirports() def airportSearchStart(self, event=None): self.airportSearchIsRunning = True self.airportSearchUpdate() def airportSearchStop(self, event=None): self.airportSearchIsRunning = False def airportSearchUpdate(self): if self.airportSearchIsRunning: if self.old_airport_search != self.airportSearch.get(): self.searchAirports() self.old_airport_search = self.airportSearch.get() self.master.after(100, self.airportSearchUpdate) else: self.old_airport_search = '' return def buildAircraftList(self): if self.aircraftList: self.aircraftList.delete(0, 'end') for i in self.config.aircraft_list: self.aircraftList.insert('end', i) def buildAirportList(self): L = zip(self.config.airport_icao, self.config.airport_name) if self.airportList: self.airportList.delete(0, 'end') for i in L: if len(i[0]) == 3: i = list(i) i[1] = ' ' + i[1] try: i = ' '.join(i) except TypeError: i = i[0] self.airportList.insert('end', i) def commentText(self): """Highlight comments in text window.""" t = self.text index = '1.0' used_index = [None] t.tag_delete('#') while index not in used_index: comment = t.search('#', index) comment = str(comment) if comment: endline = comment.split('.')[0] + '.end' t.tag_add('#', comment, endline) t.tag_config('#', foreground=COMMENT_COL) used_index.append(index) line = comment.split('.')[0] index = str(int(line) + 1) + '.0' else: index = None if self.mainLoopIsRuning: self.master.after(500, self.commentText) else: return def configLoad(self): p = fd.askopenfilename(initialdir=USER_DATA_DIR, filetypes=[(_('Config Files'), '*.fgo')]) if p: text = self.text.get('0.0', 'end') self.reset(path=p) self.config.write(text=text) def configSave(self): asf = fd.asksaveasfilename p = asf(initialdir=USER_DATA_DIR, filetypes=[(_('Config Files'), '*.fgo')]) if p: try: if p[-4:] != '.fgo': p += '.fgo' except TypeError: pass t = self.text.get('0.0', 'end') self.config.write(text=t, path=p) def filterAirports(self): """Update airportList. Apply filter to airportList if self.config.filtredAptList is True. """ self.config.updateAptLists() self.buildAirportList() self.airportList.see(self.getIndex('p')) self.airportList.select_set(self.getIndex('p')) def focusAircraftList(self, event=None): self.aircraftList.focus_set() def focusAirportList(self, event=None): self.airportList.focus_set() def getAircraft(self): """Get aircraftList current selection and return aircraft name.""" index = self.aircraftList.curselection() if index: return self.aircraftList.get(index) aircraft = self.aircraftList.get(ACTIVE) if not aircraft: aircraft = 'None' return aircraft def getAirport(self): """Get airportList current selection and return airport ICAO.""" index = self.airportList.curselection() if index: return self.airportList.get(index).split()[0] try: return self.airportList.get(ACTIVE).split()[0] except IndexError: return self.config.airport.get() def getImage(self): """Find thumbnail in aircraft directory.""" if PIL: try: name = self.config.aircraft.get() index = self.config.aircraft_list.index(name) path = os.path.join(self.config.aircraft_path[index], 'thumbnail.jpg') image = ImageTk.PhotoImage(Image.open(path)) except: image = ImageTk.PhotoImage(Image.open(NO_THUMBNAIL_PIC)) else: image = PhotoImage(file=NO_PIL_PIC) return image def getIndex(self, type_): """Get aircraft name ('a') or airport ICAO ('p') and return its index.""" if type_ == 'a': name = self.config.aircraft.get() try: return self.config.aircraft_list.index(name) except ValueError: try: return self.config.aircraft_list.index(DEFAULT_AIRCRAFT) except ValueError: return 0 if type_ == 'p': name = self.config.airport.get() try: return self.config.airport_icao.index(name) except ValueError: try: return self.config.airport_icao.index(DEFAULT_AIRPORT) except ValueError: return 0 def popupCarrier(self, event): """Make pop up menu.""" self.master.focus() # Take focus out of search entry to stop search loop. popup = Menu(tearoff=0) popup.add_command(label=_('None'), command=self.resetCarrier) for i in self.config.carrier_list: popup.add_command(label=i[0], command=lambda i=i: self.setCarrier(i)) popup.tk_popup(event.x_root, event.y_root, 0) def popupPark(self, event): """Make pop up menu.""" self.master.focus() # Take focus out of search entry to stop search loop. popup = Menu(tearoff=0) if self.config.airport.get() != 'None': popup.add_command(label=_('None'), command=lambda: self.config.park.set('None')) count = 1 for i in self.read_airport_data(self.config.airport.get(), 'park'): # Cut menu if count % 20: popup.add_command(label=i, command=lambda i=i: self.config.park.set(i)) else: popup.add_command(label=i, command=lambda i=i: self.config.park.set(i), columnbreak=1) count += 1 else: L = self.currentCarrier[1:-1] for i in L: popup.add_command(label=i, command=lambda i=i: self.config.park.set(i)) popup.tk_popup(event.x_root, event.y_root, 0) def popupRwy(self, event): """Make pop up menu.""" self.master.focus() # Take focus out of search entry to stop search loop. if self.config.airport.get() != 'None': popup = Menu(tearoff=0) popup.add_command(label=_('Default'), command=lambda: self.config.rwy.set('Default')) for i in self.read_airport_data(self.config.airport.get(), 'rwy'): popup.add_command(label=i, command=lambda i=i: self.config.rwy.set(i)) popup.tk_popup(event.x_root, event.y_root, 0) def popupScenarios(self, event): """Make pop up list.""" if not self.scenarioListOpen: self.master.focus() # Take focus out of search entry to stop search loop. self.scenarioListOpen = True self.scenarioList = Toplevel(borderwidth=1, relief='raised') self.scenarioList.overrideredirect(True) self.scenarioList.geometry('+%d+%d' % (event.x_root, event.y_root)) self.master.bind('', self.popupScenariosClose) self.master.bind('', self.popupScenariosClose) frame = Frame(self.scenarioList) frame.pack(side='top') popupScrollbar = Scrollbar(frame, orient='vertical') self.popup = Listbox(frame, bg=TEXT_BG_COL, exportselection=0, selectmode=MULTIPLE, height=15, yscrollcommand=popupScrollbar.set) popupScrollbar.config(command=self.popup.yview, takefocus=0) self.popup.pack(side='left') popupScrollbar.pack(side='left', fill='y') self.popup.bind('', self.scenarioDescription) frame1 = Frame(self.scenarioList) frame1.pack(side='top', fill='x') button = Button(frame1, text=_('OK'), command=self.popupScenariosClose) button.pack(fill='x') for i in self.config.scenario_list: self.popup.insert('end', i) self.popupScenariosSelect() def popupScenariosClose(self, event=None): try: self.descriptionWindow.destroy() except AttributeError: pass L = [] for i in self.popup.curselection(): L.append(self.config.scenario_list[int(i)]) self.config.scenario.set(' '.join(L)) self.scenarioList.destroy() self.master.unbind('') self.master.unbind('') self.scenarioListOpen = False def popupScenariosSelect(self): L = list(self.config.scenario.get().split()) for i in L: if i in self.config.scenario_list: self.popup.selection_set(self.config.scenario_list.index(i)) def prefetchScenery(self): if not self.config.TS.get(): return message = '$GPGGA,000000,{0},{1},{2},{3},1,,,0,F,,,,*25' lat, lon = self.config.readCoord()[self.getAirport()] lat, lon = lat * 100, lon * 100 ns = self._getDirection(lat, 'NS') ew = self._getDirection(lon, 'EW') self._connect_TS(message.format(abs(lat), ns, abs(lon), ew)) def _getDirection(self, coord, directions): direction = directions[0] if coord < 0: direction = directions[1] return direction def _connect_TS(self, message): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(('localhost', int(self.config.TS_port.get()))) s.send(message) s.close() except: print _('[FGo! Warning] Scenery prefetch was unsuccessful.') def quit(self): """Quit application.""" try: os.kill(self.TerraSync.pid, 9) except AttributeError: pass self.master.quit() def read_airport_data(self, icao, type_): """Get runway or parking names. type_ should be: 'rwy' or 'park' """ res = [] if type_ == 'rwy': path = os.path.join(self.config.ai_path, DEFAULT_AIRPORTS_DIR) if os.path.exists(path): # Runway if type_ == 'rwy': index = self.getIndex('p') rwy = self.config.airport_rwy[index] for i in rwy: res.append(i) else: # If airport data source is set to: From scenery... if self.config.apt_data_source.get(): paths = [] L = self.config.FG_scenery.get().split(':') for path in L: paths.append(os.path.join(path, DEFAULT_AIRPORTS_DIR)) for path in paths: for i in range(3): path = os.path.join(path, icao[i]) if os.path.exists(path): files = os.listdir(path) parking = '.'.join([icao, 'parking.xml']) groundnet = '.'.join([icao, 'groundnet.xml']) for f in files: file_path = os.path.join(path, f) if f == parking or f == groundnet and not res: res = self.read_parking(file_path) # If airport data source is set to: Standard... else: path = os.path.join(self.config.ai_path, DEFAULT_AIRPORTS_DIR) if os.path.exists(path): dirs = os.listdir(path) if icao in dirs: path = os.path.join(path, icao) file_path = os.path.join(path, 'parking.xml') if os.path.exists(file_path): res = self.read_parking(file_path) return res def read_parking(self, xml_file): """Read parking positions from XML file.""" res = [] with open(xml_file) as xml: tree = ElementTree() tree.parse(xml) root = tree.getroot() parking_list = root.find('parkingList') if parking_list is None: parking_list = root.find('parkinglist') parkings = parking_list.findall('Parking') for p in parkings: name = p.get('name').split('"')[0] number = p.get('number').split('"')[0] res.append(''.join((name, number))) res = list(set(res)) # Remove doubles res.sort() return res def read_scenario(self, scenario): """Read description from given scenario.""" L = [] flag = False file_name = scenario + '.xml' path = os.path.join(self.config.ai_path, file_name) fin = codecs.open(path, encoding='utf-8') for line in fin: line = line.strip() if '' in line.lower(): L.append(line[13:]) flag = True elif '' in line.lower(): L.append(line[:-14]) flag = False elif flag: L.append(line) return '\n'.join(L) def reset(self, event=None, path=None, first_run=False): """Reset data""" # Don't call config.update() at application initialization # as config object is updated at its creation anyway. if not first_run: self.config.update(path) self.aircraftSearch.delete(0, 'end') self.airportSearch.delete(0, 'end') self.resetLists() self.updateImage() self.resetText() # Update selected carrier if self.config.carrier.get() != 'None': for i in range(len(self.config.carrier_list)): if self.config.carrier.get() == self.config.carrier_list[i][0]: self.currentCarrier = self.config.carrier_list[i] self.setCarrier(self.currentCarrier) else: self.resetCarrier() def resetCarrier(self): if self.config.carrier.get() != 'None': self.config.park.set('None') self.config.carrier.set('None') self.airport_label.config(text=_('Airport:')) self.airportLabel.config(textvariable=self.config.airport, bg=self.default_bg) self.rwy_label.config(fg=self.default_fg) self.rwyLabel.config(fg=self.default_fg) self.config.airport.set(self.getAirport()) self.updateImage() try: c = self.config.scenario.get().split() if self.currentCarrier[-1] in c: c.pop(c.index(self.currentCarrier[-1])) self.config.scenario.set(' '.join(c)) except IndexError: pass def resetLists(self): self.master.focus() # Take focus out of search entry to stop search loop. self.buildAircraftList() self.buildAirportList() self.aircraftList.select_set(self.getIndex('a')) self.airportList.select_set(self.getIndex('p')) self.aircraftList.see(self.getIndex('a')) self.airportList.see(self.getIndex('p')) def resetText(self): t = self.text t.delete('1.0', 'end') t.insert('end', self.config.text) def runFG(self): t = self.text.get('0.0', 'end') self.config.write(text=t) path = self.config.FG_bin.get() options = [path] FG_working_dir = HOME_DIR # Add TerraSync protocol. if self.config.TS.get(): arg = ('--atlas=socket,out,5,localhost,%s,udp' % self.config.TS_port.get()) options.append(arg) config_in = open(CONFIG) # Parse config file. for line in config_in: line = line.strip() if line.startswith('--'): options.append(line) if line.startswith('AI_SCENARIOS='): L = line[13:].split() for scenario in L: options.append('--ai-scenario=' + scenario) elif line.startswith('FG_AIRCRAFT='): L = line[12:].split(':') for dir_ in L: if dir_: options.append('--fg-aircraft=' + dir_) elif line[:15] == 'FG_WORKING_DIR=': if os.path.exists(line[15:]): FG_working_dir = line[15:] elif line[:7] == 'FG_BIN=': options[0] = line[7:] config_in.close() print '\n' + '=' * 80 + '\n' print _('Starting %s with following options:') % options[0] for i in options[1:]: print '\t%s' % i print '\n' + '-' * 80 + '\n' try: launcher = FGLauncher(self.master, options, FG_working_dir) self.stopLoops() self.frame.wait_window(launcher.top) self.startLoops() except OSError: self.runFGErrorMessage() def runFGErrorMessage(self): title = _('Unable to run FlightGear!') msg = _('Please make sure that paths: FG_BIN and FG_ROOT\n' 'in "Preferences" window are pointing to right directories.') message = '{0}\n\n{1}'.format(title, msg) self.error_message = showerror(_('Error'), message) def runTS(self): if self.config.TS_port.get()\ and self.config.TS_scenery.get()\ and os.path.exists(self.config.TS_bin.get()): self.ts.configure(state='normal') self.ts_prefetch.configure(state='normal') else: self.ts.configure(state='disabled') self.ts_prefetch.configure(state='disabled') if self.config.TS.get() and self.ts.cget('state') == 'normal': options = '%s -p %s -S -d %s' % (self.config.TS_bin.get(), self.config.TS_port.get(), self.config.TS_scenery.get()) self.TerraSync = subprocess.Popen(options.split()) self.TerraSync.poll() print '-' * 80 print _('Starting TerraSync with following command:') print options print '-' * 80 else: try: os.kill(self.TerraSync.pid, 15) print '-' * 80 print _('Stopping TerraSync') print '-' * 80 except AttributeError: return def saveAndQuit(self): """Save options to file and quit.""" try: os.kill(self.TerraSync.pid, 9) except AttributeError: pass # Save window resolution. geometry = self.master.geometry().split('+')[0] self.config.window_geometry.set(geometry) t = self.text.get('0.0', 'end') self.config.write(text=t) self.master.quit() def scenarioDescription(self, event): """Make pop up window showing AI scenario description.""" index = self.popup.nearest(event.y) try: name = self.config.scenario_list[index] except IndexError: return text = self.read_scenario(name) try: self.descriptionWindow.destroy() except AttributeError: pass if text: text = name.center(80) + '\n' + ('-' * 80) + '\n' + text x = self.master.winfo_rootx() y = self.master.winfo_rooty() self.descriptionWindow = Toplevel(borderwidth=1, relief='raised') self.descriptionWindow.overrideredirect(True) self.descriptionWindow.geometry('+%d+%d' % (x + 10, y)) self.descriptionText = Label(self.descriptionWindow, justify=LEFT, text=text, bg=MESSAGE_BG_COL) self.descriptionText.pack() self.descriptionText.bind('', self.scenarioDescriptionClose) def scenarioDescriptionClose(self, event=None): self.descriptionWindow.destroy() def search(self, entry, list_, build_method): entry = entry.lower() if entry != '': build_method() L = [] for i in range(list_.size()): if entry in list_.get(i).lower(): L.append(list_.get(i)) list_.delete(0, 'end') for i in L: list_.insert('end', i) else: build_method() def searchAircrafts(self): entry = self.aircraftSearch.get() list_ = self.aircraftList build_method = self.buildAircraftList self.search(entry, list_, build_method) def searchAirports(self): entry = self.airportSearch.get() list_ = self.airportList build_method = self.buildAirportList self.search(entry, list_, build_method) def setCarrier(self, L): old_scenario = '' if self.currentCarrier: old_scenario = self.currentCarrier[-1] if self.config.carrier.get() != L[0]: self.config.park.set('None') self.config.carrier.set(L[0]) self.currentCarrier = L self.airport_label.config(text=_('Carrier:')) self.airportLabel.config(textvariable=self.config.carrier, bg=CARRIER_COL) self.rwy_label.config(fg=GRAYED_OUT_COL) self.rwyLabel.config(fg=GRAYED_OUT_COL) self.config.rwy.set('Default') self.config.airport.set('None') scenario = self.currentCarrier[-1] if scenario not in self.config.scenario.get().split(): if old_scenario: L = self.config.scenario.get().split() if old_scenario in L: L.pop(L.index(old_scenario)) self.config.scenario.set(' '.join(L)) c = (self.config.scenario.get(), scenario) self.config.scenario.set(' '.join(c)) def showConfigWindow(self): text = self.text.get('0.0', 'end') self.configWindow = ConfigWindow(self.master, self.config, text) # Wait for window to close and reset data if Save&Quit button was used. self.frame.wait_window(self.configWindow.top) if self.configWindow.reset_flag: self.reset() def showHelpWindow(self): """Display help window.""" try: self.helpWindow.destroy() except AttributeError: pass # Find currently used language. language = self.config.language.get() if language: lang_code = language else: try: lang_code = translation( MESSAGES, LOCALE_DIR).info()['language'] except IOError: # Fall back to default. lang_code = 'en' path = os.path.join(HELP_DIR, 'help_' + lang_code) readme_in = codecs.open(path, encoding='utf-8') text = readme_in.read() readme_in.close() self.helpWindow = Toplevel(self.master) self.helpWindow.title(_('Help')) self.helpWindow.transient(self.master) self.helpWindow.bind('', self._destroyHelpWindow) self.helpText = ScrolledText(self.helpWindow, bg=TEXT_BG_COL, width=80) self.helpText.pack(side='left', fill='both', expand=True) self.helpText.insert('end', text) self.helpText.configure(state='disabled') def _destroyHelpWindow(self, event=None): self.helpWindow.destroy() def showMETARWindow(self, event=None): try: self.metar.quit() except AttributeError: pass self.metar = Metar(self.master, self.config, MESSAGE_BG_COL) def startLoops(self): """Activate all loops.""" self.mainLoopIsRuning = True self.commentText() self.updateAircraft() self.updateAirport() self.updatePrefetchButton() def stopLoops(self): """Stop all loops.""" self.mainLoopIsRuning = False def updateAircraft(self): """Update aircraft selection.""" now = self.getAircraft() if now != self.config.aircraft.get(): self.config.aircraft.set(now) self.updateImage() if self.mainLoopIsRuning: self.master.after(100, self.updateAircraft) else: return def updateAirport(self): """Update airport selection.""" if self.config.airport.get() != 'None': selected_apt = self.getAirport() if selected_apt != self.config.airport.get(): self.config.park.set('None') self.config.rwy.set('Default') self.config.airport.set(selected_apt) # Let user select only one option: rwy or park position. if self.config.rwy.get() != 'Default': if self.config.rwy.get() != self.old_rwy: self.old_rwy = self.config.rwy.get() self.config.park.set('None') if self.config.park.get() != 'None': if self.config.park.get() != self.old_park: self.old_park = self.config.park.get() self.config.rwy.set('Default') else: self.old_park = self.config.park.get() self.old_rwy = self.config.rwy.get() if self.old_rwy != 'Default' and self.old_park != 'None': self.old_rwy = 'Default' # Translate rwy and park buttons self.translatedPark.set(_(self.config.park.get())) self.translatedRwy.set(_(self.config.rwy.get())) if self.mainLoopIsRuning: self.master.after(250, self.updateAirport) else: return def updateImage(self): self.image = self.getImage() self.thumbnail.config(image=self.image) def updateInstalledAptList(self): """Rebuild installed airports list.""" if self.config.filtredAptList.get(): self.config.makeInstalledAptList() self.filterAirports() def updatePrefetchButton(self): if self.config.TS.get(): self.ts_prefetch.configure(state='normal') else: self.ts_prefetch.configure(state='disabled') if self.mainLoopIsRuning: self.master.after(1000, self.updatePrefetchButton) else: return fgo/src/gui/__init__.py0000644000175000017500000000000012143234652016121 0ustar robertrobert00000000000000fgo/src/gui/tooltip.py0000644000175000017500000000400212421501134016051 0ustar robertrobert00000000000000"""Tooltip widget""" from Tkinter import * from ..constants import MESSAGE_BG_COL, TOOLTIP_DELAY class ToolTip(Toplevel): """A Tooltip widget displays tooltip message at mouse cursor when it is over master widget. Arguments are: master: parent widget text: message to display bg: background color offsetx, offsety: offset from cursor position delay: delay in milliseconds Warning: ToolTip does not work properly with Frame widget. It seems that '' event have some problems with getting updated cursor position there. """ def __init__(self, master=None, text=None, bg=MESSAGE_BG_COL, offsetx=10, offsety=10, delay=TOOLTIP_DELAY): Toplevel.__init__(self, master) self.offsetx = offsetx self.offsety = offsety self.delay = delay self.id = None self.create_window(text, bg) self.bind_to_master() def bind_to_master(self): self.master.bind('', self.shelude_tooltip) self.master.bind('', self.set_position) self.master.bind('', self.hide_tooltip) self.master.bind('