dianara-v1.3.2/000775 000764 000764 00000000000 12615166255 012707 5ustar00janjan000000 000000 dianara-v1.3.2/icon/000755 000764 000764 00000000000 12166364170 013632 5ustar00janjan000000 000000 dianara-v1.3.2/icon/32x32/000775 000764 000764 00000000000 12332666561 014421 5ustar00janjan000000 000000 dianara-v1.3.2/icon/32x32/dianara.png000664 000764 000764 00000002257 12332701227 016521 0ustar00janjan000000 000000 PNG  IHDR szzvIDATXåOlTUk;P ,0 n hܑ,\1qa,c$&,ѰhK[eڙN97L;LgZN2f޹;9;S]{A{Y+ 7`5ΜijHG}Zl gZ.Յ&ݝ,j6w©`n ?տ&+Jk `m._LaĺrĿCz}$ ڪy^kZCv Kv|LHFDzO IQ}-K}iS 3^P4, V33VLUZ10Y$"Yi r&=ä m潘L&i˗ $c@ @!Ui,ـ󑠡ccL% rJWsJcOحWڙu?M3Edd0E'PrP(XD,8 APP@A y >Vee+$.l^q|qs FC$;)q4Θ j$ yf{VTuu % XeAfc#h8@#^ wGƁ9te e3kk'\@h?'``V$9DY uPm? vmYv/,U!D4 #HMg{\Cz!_xo,V[}_c>D4@V`u4ܼ*@ߡEBk |ڝRR# `'=uݯC$Dl Ret+ 5"}>Ǟaf4o<dUvbΊp8p@r;C'%]!^*bqSW~_J&WV|d(NIABEں]' bg{+_2BKD7%, |f7͕A#`v+AqTȬt<7pꀣFFi}~_~a|~ol?S1 daWg2v.#=AfS]t5)T^#?ʑdrR]$BzfQ!K&D )ǾS/+ҍ!!V?$kje n\{x=2TXVIE !A̳8)d1JtcDž "C'W}}3̶WbϪNIvtִCoၒٌ.ǃS]T(K*r!3N魄3;$ࣻ߰1X0Dt1Y˴Ve 5"I_rv2%:w0@nS]VWR0X0$8)gJvSIH߾e bœn SlWoP5)#vw}V,wa[ivk7BVV, ?ȑN ?xsɅ.n~}Gd9> P2/{SK|ڍѧ+dq-F}_ú蔹>^0\b>xcW*n6?6;(=C>mY XMPUCB ȄT i! I ˭:n!Ppn-?*([kp>ߍd;UezcxNVvt/6æv8ѠZ.u P 8"ģ8,,?ugJ{ ܃OOw3O=ҽ^젠O,z|ꂮqtHJ^a}Bf y 'kyíĈjrNP*e  E^͎yeϏ 'B`AnlA+8G "OX|jeQ9'י]BG6vXQ6B|?3b!,,+mYHq-kU'2M=;+|aq ;LܔYnJhQHu4(TQ>(d!7Bw^MT:13/_sn;sz )=!23!~*Ѷ8`~]:PIX%Ui ͗˅ms^i. 5}qu3 Lsxq|  { ;f C|@1dƐ)͊Pv ηӺxKO͹e>ްIp|-|µ 2A؇ߝ*ܕMhm}LY%*f`ӲFl>of\\~We([$Hǫr.k8݅c3l&KJkcMwdo3%wZ&o_ M奶kO^sZ Wn?,htBFo ܆Бغ`n O%lT,o|-V˩JO;EՕܮsF᛹d6]Us e,?d0Fk|X7I03GCSYd?L0gI 5+r[Y^_iPBEaߊlKtl`j]3ѧ}[5ILuW%4UO%M'o*ZԎn'P"i֭59]LW#4vD{*\i=pX?%4/?b[b.ӇjvT㻝KYdX3A]WI'OPhKgWIz@qFփ%`e^q];'u-,lSތGCr Lǐ`ݨWْoe5/g2}+޲ĦV\L+@nC:f'l6(b`CDgQ?Kw_!Ud=@1׹5Ӏ|F>4  ^|{#%j]58+ 4M.n We}yfTazX&7{Ke*˫ ECk@WQ .i ]ݦu@6FJեQ7/>~bzK9aDq_|&aQt#;u'3_@y.')q+`*~羠{-.nе+2EMkG]rՇ٨,7XpQ&M[Y wU2\0Q樳G8q% Y*}}Wdxz}JH+myr3⍫TK cs*q{tP^*Hl5,N Q閗)q*,8:ˇ;cn#u%ST.iT֤^dV8S=z$Oz]f"aU)䮪;( A,'=~N{kmX<`j'/䇗@M#0>U nfX]쀑"ЇJ0|Sxae& -uW”xԋBO2Y rKU)؄ nɶeLr?/'^>Ga?ܰte31ks_( վ7:|, ?1e5WKZ}U- {H0f(&x(B0Ȳi^(+KMq`1Gς{1L?uZşX ]/3KY!L^mv5t"`;l;_´sv` r| 2}@!b״fN5W5B cZ]QϐNz< /+kacU٣=yGL" oL=:<^uUƝ$qE;D!֋CuIR-_ojЩ_Cc2M?a"#iB,O0p34mF)Ǚ*#Gр8GܵBFB/<XiX)! |pve::rFDžF7Ci]jh}&HG9S4D0Uؾ2窼GO^5훒oΩ-oBY q#޴7/}GW7)~Z_%{VIՋ˅/lm諟O}A]n/aK M3A]  *:iFr-H?4y@'8 s_RĻH kGkYqTTgkic+P:jĖef\m٬)W&If@|*G1Pm"v׍ ܮ'*7&3t iQc7^8/V?m: VҺssM].)PM} ܻ벁1A V@ۡ3M&k]r 8ZpV 'nN}37Y7Ԫ|#Y)&bp@Bb"7 "rNq"kǀ8L.$B{b _ .)KDrk[˔!J:؁K*`o8y@LQ'-tCem\Q8q@fbܥ$B.QՙWu~^РX]Sޠ͉"&7StdYNC.bk,'J)!'Ō I{ M)m.H*0ԑ}H@}GgmI  BI{DsCMrEu!B;.n<1i?Zr~r$'@%* 8d%ѣ@o|}'=/?*wZsay|j@J1 >֫"|> ()s(+Pmg`2,lmB'@&tN- 2HonTvc6Mل,ma Bm%RBCTxв܁{`ҭ'ou a&$uXAl}}}uCFLfVq_gl:uh'vZ <sMXO :Zo<_+f ;&i\_,DsOo-fZ8cnqt nG23@7Y,;nG/n.%@,PMsX-rҮˀ_}Ѐ%л@/~ 6.3KkMpX 0 G{y]`:T56;M]ŕ@TQz'm)˞di j*MhLO3Z_] o6˙ӍfY_ #őbE>0ԲLE[o8֐% * f\~ yS O 8ZPeϦFױG(:;fq;@ShFmP6x)CU>Q:ntN)^B$>A:*t-#H4ޅJ3@'RpTofN)?X7} H~ +6)nT~pA\NzoF<md5,Skt+EaTߒ \?3/ .jBPZw=u,HOGʸs@9ހZ>d֋.fBJژ.+3IX7C :"|=A$K>'WӕGxUǫ#oRRz4+v("/XsgC~c߲-\CգZx,= ׄ߀F^Lnx[1H/}q8kx!/|J еǓ@@CiBO@{ζR}L 2yNb3mmp|LcjR[>"܃+|`Rт rQ0_6[}R[ <~h4pxƽDŽi~@Bήe'@8;{r7`v_o,u 'R eM~w:xnw*فPMg}2C!Z>܀c 1ɝCڃSTD{/th{ <5t4 dx U2qS<"S}cp \]*訪>gZIPBB JGAPP4A&E+"U(,T&^H/R.A$Hf޻֬<'egdm<8M2pV+NH!0:]TyMq#R4Tug1|hRS<<#{֓Ζ[(w2[;9ׯs&fbzK9QU.jt5r!mC=νX+0 7 qQ”F:2β'k Tc" íj OO AjPK<ĻsrG-^b9M]EKu-gO~0\)]k 4@.H=jT& ĺT%:d(PDic@Y ]@{`zǗ"/'ZR!VsU[{TܶR<[AGRFɝɩ ؁*lh?msΟ2 .Ii0JW7Zh<@drB#{V >5t0& `Gb a5hT4\sg&7TCqZ['ʃY&UxLh}y_&uٝD.ҵ߁8|l!22Aa*A|9Θ7 m4]W:Ѥϖi ?Ҋ&2SK՛^^JS|_ʱ]5>,IO*[r[1}W43qaSthi8CByX% ]o. ###[g;,Zׂ* v:(;(:H,:e sb$?@sh\PDK)um[IP wZ%]jMpY)Ou}A0;LgRf^`t5Qzr>R@ /1`+HOy`(L؉鮿 rJOyNG5M|oo^+->3x^/:mvFK+G|=۱3{4]T?Z(<i}b_?ZlN <]xWV\(昣ŽJYpd$bQ7tny@J+Y[U)s9@3\xyA|?k`R?)!\+V,Y90c1^EDHI) Qq"8cʃ{IIOC!{VHZH* oJS g8a0GLwmLnϵnAEqk)ܨYP!'[!OUύ ߂xkzFx=gROx^^YL6YʗYU=EavYs_yC_;TZ _ݺW1u§n黒0ZLI0͘oO#'[%7}BNA8w7F'5-H9yL/׻n]QS^5-*pk7U3Қ.@7P:O; ?p{x,[B'" #CྸjJ~vE{pkCgK9gtH9+édzg|"-8:>daYgQULߩJoj'+П*zzYݺ_?]}3^v4617e4K,xK-ՓL: LQDnî#.D\_R']כX ޫfgUֳzµTP( s [_j?9=-PzZ!yUl og8j Aí#!mQہpyP^B\];|kznXÁ&HG%柾&7-( U˲5>~{޹X俵F>;+\g{˱K_ E~HI 4M)0f"lԏ[g>4"'vcC_WX䏨{(jfB8١p驫@dd 79 ,KKU&^c|&f¦,8GuL3y/EL}< {W>=u*Hۿ=vU՞${ 03ԲDlvR_\G}i`P@M9U|6 6ܸtqʙzhIf/?r+w V IKq֫Vɓ#^?eKDzzt[V#6󅧶fQiKZͶB`<*M~[ǻsk6Ĉo"Wcoowu 쎞~^eP)v55z?f_{bCW!lpo l%H:OK>j,5>V LGyI}N=zxᮑiBވЬIG~Rϸtpw"Vx|f8cG BKȎf_}ଯ6]N7::7jwwױGv5.hwGvz'u;M |{k<:+^Lϲ?+m\ Rm؍+zyuޢ rDb3O7ʵm&P%o]=RE+T9:Z*2t  &˽8 *'m"% ko^¾˷uuztԫqcd$])7'Cגʃ+4gXu`oNdG>2Fܗy}9?goN. fUÕΌ[t蠟ywOz0'T<>sL+w_l;63h5_U#Np7ԛo닷ޅ`a5O&q}|- yeW_/r!nn6O!fڗe x&e8eH]vK))%HSD>GPrAp7/J}tF(8W'A;g2V,akLV>ZVj z 6<4]:H[jr8bs'`L?߱HE˗zzQ˛;a jsR'MTAEԽHfre‰:uy T=ekY5wAγ^{O>5e0f""1$ uM\#LPl(9gpK}Np`a*\ t?̖'\>ace)Yh젶s||S y8EGƂ<%׀81AZK CC_`$f8 j4 41~Z7ҷ| yOn6~ @2%AcT}!+𴒿MùVH8z'bd{Իum׮] 9:##c#E/R?Q,(,mϺd u5/$o& ,PNayBntOyTN2W$K54!mc)H'KQʹ`-Q = &$IUCxʑ)GCyǤ>9`f`]ܞL 9/6C>)I^RH 9\XN7tPԩo5K}/?|A0Mqb:Z}Y14c}dŮ9؊c6ǙK5mE#UD^&DVCLUS<`0I+qk/LaU3ɏt5+Y.Ly-(M%-Lea!zxߙgDqYcXL*ʕGd>֋rBoL6M yU%~$$[/K;_J7QSyD(Q-!{NC\Q?V ;CM(F ř_59b_^*Qc^zXkR*;jr, _QDDYzҲX1GQ<5&ꁨ늍|#}P&͢\ԄiQoGnN܉ԔOً=:o-!byqv)Vu3UqGNs wY*KdAja7_Q:/,锗M<1 fiٝ#Ly\69&FǒԠ:R; !e,b*g䔜2%YJI)S#HRT._ VCmYUt+];N5/^ףs[~NkcnYꗿ<_C6mooo Z7kg'k2 )g+'gN:廱L rׅ`JT ^x#S;!͚>aNC2eUIMlq *.,  ]+}oڄk095ph^ Xх\NXɈZ U)BLq,U7eO#mGFqנ +{sQ,bk/!u]0Д#s9jJJpADfTPl Z[Vt߅vh Ġ@ ^}-6oMسY]єo4r{ 'TW.8@XXԗZ Jw6ub/ԧqx)h#CqQ־/-FFƿ=/n?;ښABX?# $__ ~\zG @f3謲ʦ le8C+Bj +}E@@{V'}: $9(4%%l.7{J˿66_\.8L6Ovݏb㴵kICr犕^ycSG1?;cB<~6UN5]yx]6y\ Ttԡ?YьlY0!V<=|:*){pcxR}$<*9" QhBܶoy^`|vvpH/o9偪pTtvRM^#mˈ$3*۾'G.s,nWo9<<:!"}k!V|@  Bh { Mrs}Gw 3Mv'hAQl 6\i/cfl[<+N_趫뚺(- ١!} 9e^*u9phV})Hr :t"jH|t1~ev,9&]3Zӹׯ 2RQ(ES6u߂vPj D:mJp7#|p|8wG}v2@(Q ,!2]j߶2  |"=-'/ -(qgdǠ2E1fUr.቟i;ƨڡɌ^C)wbA䳩uҞLS͹ y7)B1DCh2, :*؅ٌBkCGͼ>ΜJjzq2C.Vq 2# ˧(äEx'm<]E1)z)cdglxbU2?d!rDe9E\˥I-3ץ ZLɎVec+%zcqAQERss(PzR<6'v/&_M@R8,RmD๎ -JE2,Ԥa%{JȐחlgY<8|M8]qI8%P (OZ*`N'h$itz47$Ui!D `,&GՇߺu(<#\D]lIe,#ҾHj@ZXI3Lo{UǕh,f:Q_dcid*rC%|GX<M3}DV1i )@ Z K^[d!VFMt _0Us9I憢V) <\=is>E{|˶;=*c-NƘAF\k!:0>"BL %HZo*#=<|dДXTe`lxOC]= ŶN2cud?%'?UqYF`Ze?<2^{?~,9 =҃ҋkhmm=ξw3MnME 䲩+?"wbXl4M13A:Z.}JM?>|vNR\]3:m{7h?E*ʌ )i,,ɰF@`{4=Sptlls~|况zoA?+Z2슒uٖ}ws>sjsS>oK&u9q왞BG/Wر-c n߂mv}L|hی3 OsUOsʄDkgW@feyu@'ۇgoߢ{j)M{>ա~V)'I^a\kA퇘ęط-LaTiwIF3+/W153 E(<]2#5!$!uVG16@ !BGXcYd`,ji֞j bD:^MyN\69wAdCJHw81!2Ơv9b ul{<_uF56ڞNE_Ti=mA[$lʹ8wMXؘ%@D^ck(pW564֞hGoFwmׁ`.Au\ܓ5 r,?!$p{GQP҃'{g !܉nύw#zڻ0T-4 QJJ7(Y 'LJXLx"2XQ:4yLvrƦ02uEQNVpH̲Dm0xúz̕1} gGF0md|T[k8 $K+A8dTV0 10o J(2Vv@RYf ^ ?Ll>I2,]G D"hÓ!h"DS@W.N.-Aw .@9 A$H('܇5H-rkea;Q73L9)[cUe1edu~qBkx1P +)^ {zVT3 !%B.j!%=@YyK B&cY, 0@M`;SU\kC}%@{DxNڹDвNg|M2 G*Jr$ ;#RMH&bb|Lh@)p3 TJXF+E( 8P2JR1 (DF+F! , k<)D q*VPĮ,fdz^FAEQKڴU)@=1B.Rgނk3nRDVނF8K6@)-Ȗ"C"ivB.sѱH3xݙy3e,h0D~~st8H/CNֹg޺+ĵ` ăB͛'Ol. X_ǻׯCNMRB7braA47qPښ|v4ɽwt<ܹcW^|Ul4>?\yp =GN"!MT2S{Ur?GBժU[|)pAvv֓ahB2`p~ cX;;?Ϥ1eeBZWZZ>^YQ숧#'L S~cxR"`ǾV6/ ZK(‡׮ybYT9A*5r5~Z#%Oӧ<2Ge174 ;\fM?G V~ vozpnŽo/j͌<74X% $%Ѥb0Jk5 =زKȐZYI%mI"4`E~=BFEB&) `,cX'ߝ"zTXtSoftwarex+//.NN,H/J6XS\IENDB`dianara-v1.3.2/images/attached-audio.png000644 000764 000764 00000024217 12163340053 017526 0ustar00janjan000000 000000 PNG  IHDR>asRGBbKGD pHYs7]7]F]tIME )\ IDATx}{eGy߯s_ffiY=A1 +CU.'8V2LN ) B+@ll"YQpR60"!VIvvwfvuݝ?N};;wf{n̽9 \9W+Ǖqr\9bWsc_ P]¾ >KPKDz}Lxkzt.ؿ~t1\p!nжl,*1}0RBR0F.';.jΙV)Cc7!y_T-5؛A`@ ^ @J A`՞c5}Ό.k2<{_ݶ ZXaJc`@JbD 5+}@oo\JYRARB)(*< UaeЂ=NLK @>ٍ?3?O04~>9]:JGDFʜ%`9fa?>ƞް/RL>F9`-3gfB.A(#tFr󇦇R QӠ >Һw=8o3 ,{e)9-%7t\A0=5$Bpd?ֹ T. dv4YX?A(,(?0D9H a7|`& ecƮ3iQQ#ĥijRx8IrژxVYV@ OeZ])UGȔx&Ja9zAQ=Hk֥6n;e>GȔ "S 2 J0:3SbEhA=Px!3JYR0Zk>XV P,D1.:g)JNb \J E$i GsM5eG\SGS&Dn%8`^AgiP qC, 5,9V(e$J;x0YµkȲ Kx&FrMΩ %$sDW'"+Ѝ)kN4Mi") -&(ñʲ6J*M{^43$s h7L8` ;%,_P+. B8*-NRʅiBJS:<@$=G6Pq#"w,8FVsv`toeK![9 i| ·,eYopMVRP־+ϑxXO^PRƚ)`yc}EĮsF4[1b~N'nb!l`;<[%JRbل`u 4q Kӂ%CNK9H )2!iTe , "n"RHy:N_~ O1p!.!IBX_.r\8FMܤVFa3g\Xr fY4[TB䡬`dh+4M!t;&\6%.[1<(3+XN}@eRȲ |l`<3k5ٳh=kʣ JGqN4jz~97{0mnner>P;e 7uRʾm(.ѷR R3Jjp^nR t]q\1_zc $q 삇# ,}m']MnBJI"/(Dgz Kii!V'ҋ~s3amOx(eH9nn,CصB 'OXcl6 ihcX\LU'MXXAc{miac ) /w_콱4lc&IR6v'YGJ j&>GYFz1 4S.\>n` *+T $ y y\ab#%0!\OΝ?_l@M';/%V 9 =[n >3Kd(I}#6)njNW*goX+=gn;677tW*  8,r#F3}P)U[d=Ggܱ3 M8W 4<$w&Qc 666WلV YPX;JH4;J|~à 5sI){a{ NFUulnG {it\}_ut;AUaH ^֑B1&Pz4 u7lP"۬ g!e.g0z%eRa+ϱn,k. `kqs MO{vYG"RJ\pPE|صK)Qr ,hZԩSlllZbvvsu666JLjgv7V e? > ?9beILsCR) )k/xiZ{ɲ Oƙ3g4Mj mΥ/>,˜fsMKoFPz9Ó6sssz?_DÇfnn@+%̔`rr ӐRѣ;& @Rq[` Bb_~*x9GA^wY>ѣG!B(077[O B~iĄN^ǫ_-U a^ffƁR 2DQ  .vZp`1h4yk8@kZ(\3nkbbMЕ9[T*κdi% 9OL/--AJYs:55U(|"}>}oaa~cPՐY-}N A ah ]zJhxEZëq бEVX\\tEFB2J9=ɦ0;|rm& ^C@?0==W]$r:wl{p >(v؃|n[+099Y}DZi9 OLLN-NPyH>P=Ɩk<qܙ rA>B ;oOZF VGL !055ZV4`x3(2&YXXTt_|0pffk16 * w!!M'rR5HLðO(*6Ns5A e{BiZVf (Vx(?8rHMz7IkG6L;]Vv); zr!V.Wf'ePV|zߌ +`R>_A; CDN tQ;&IYofP 0gggK3þ_B܌-(n-3@_y~Na [x=3M7`2 Q>P퀜ZV{/ ?)1!iB6-)K~)vaaW* [< e2B:23e|aqD0<92 $q&" ;55UZQsҶ܃/OZckPo|ۋc A= w˴X76BC>yFQp9O OL||J 48???0o@{Y@k(X#G8ō2 8k4aW]@r~ՏUS]nZ-d>ˮ!ߞ^;a4\R2 Z,ñc.~0GGEf-q &Z@kF<g_؍x΀4|?ϗ&hs%z/BoI5+qTii=g R]A ,@qlPL^?o*[Jɲy٪$زvO'c |嗿8n;t N7K|;E_BkKS? !|AQgk}̹ͫ8|䈛} zؚm"AIv{c;5$ Q\͌ڎcm=Ru4FWQ`Ԙ9j g[U$_ _^v|y;lK6Dcoޏml] 9y~L0˸ ӿp[Nu? xq/@Kʗ-k0}&;j˶V7nHܲ-bm4*2i`@@Hgn pr[/WbnnPJe'm IJ@0}L|Iٌ02@R(mv8 Y@h})^8^ZmІz@`xbAbSW"n #aq53i^hʠ)I:1d:+_4eM7;Q5w_itў%fartJma`B) 37 QIڇ~-N˦ 27_.O<(@_y.d<냾@Co@ NS((JZV3 𖥷Ok}}}@cN<$B>P C_IA7 b܋7}|8/>E@z,6zhԠ&'''?/زTNDqୖJՂ>7ǦvJ^X*)$1z(["~AQ _*t=]:Ii_jBZod6'n `@RO ; ;aIbVLɳ}|\:w;F(BY:#'w]4 zÍ7uKʨA0h_z@Q؍6]IAݤw^Nv_;y\;nfi6o_p9h0ؿ,@*||Y0ja< >zk6DK/ϛӷ߷߁k1c#W7vSiO$0F@ <ш Zl6iwn "Y[=W9*611y΢X^^6'_x!~w/NRSKvP35U/⑍q ko]{; Cw=FD~oU<^ad'YNZ-v:&n;{־ n)K n3}ЛMC%??GxvGPW5/; 5[Xg"Oc]U@U!  la _;ݮUo~K_z Eg`Lz<'k-+DV2!!'gמAPF-"تa5xdQ C@ aawV~ ,˧Q 6ď .{ݧJ %8, DiS] ;~ i%*A4bVVql?&dhoM1=z ƥB 痐[p!.4W!ȡS΀8M`J54&QG + Յ춑mB,~sr ^s}_WGLH (A Yg`vhhgOU0)0N`6LPC=UOw[.)$]M7ۑ$=6:?aH>1YƣOs8yM )D5@4p}cx10edB,YĄ{wk}&y%9aXF0 ::Ν9?zO/g.]Nĵx5/ǡËq+gYN,[nfAJ)SՒ7-?3pD \WkU0,VdJ'mW^w^%>CPo<6cfffo|3?<-{9T0;W} AYb=3==->]w;GֻEE5۬brqb v$7/D^E)MVv?~G¦v7:.7+u*DArǟy#ߟCMpyKԻ4Jr(IETK g,X͎mX`y6QL +ybB]uLۃ_L41+Ǖqr\9WQIENDB`dianara-v1.3.2/images/attached-video.png000644 000764 000764 00000030665 12163340053 017537 0ustar00janjan000000 000000 PNG  IHDR>asRGBbKGD pHYs7]7]F]tIME &}b' IDATx}y]ey֞ϐ 9+U D۪$ 6%@d3@fF5 5t@D "RPp=Ѐ?-}>{yfptLq~wo PyJlG=1Cğ։=#I\N g^rYDGS12u^bfY:w+v? 0">=pgDh9'$#3E]0~䎏? rMy. B Azط'a*E`w9G d/)8^Thb#=L!-g"ri9;kXvmU|foi 1{XfsL$Ir͞X;?kaH?_*Dp|%ĘznbcvIRBRJ1Z4G;cF[UV$(%%$"0ksba}\-}RS _(Ep7N7:#ֿ5q'"8)%(9[CлN;]lIa$5.laT* #tL2vj/ӎ.%P+=Z@)Pb-\F!f1u #gV'I FRBGn&YH%{2bTJ$F O[cҞqw lj`γDlHDQEsО@@%%1[Y}pm\FI|v4a:Q$I+f!F:BgtKNzH|G$8ؿG4Yj-s$<+" -w{Ψ]FUXwFt[#&|'f 4wivdjF.iAyI"R-K3ZC#AE?Ap)~<݆x4Pk0NVy8 dMq`AhSAFCze_*R1cafS|X.d[L b!Q9ɳ@Fe 1DM^kPFHBY9F($δG b#b3!(Z*zf5Zvب#ݿ 4X ^Z vjY5+=8Xe/hoQte' q%gEaorpRI48Hpy B9o_1@=Ytc]] e9L%N3->~kf GX/ ^ pp޻0L $V"PW0%~:ʜOƝ5dGGd b0b&PͫƳ;C4iXngxN;1 "ؔ>£pDe(;KGp cI1(fMQz2H@|~W+\#'ȭbȫFWhc՗/6cٖZ>|f-}6.|,NpaKb'{  " B"2qpRʴ5G#->jAK)gDZEBIP1ЮLEt.I؅!aS@WBf(g*@^(ԗHqt=_:v6{CS:"V =gz[oT.p{sg%A y @⸶l0rw3"e.t`{^Ho j 3 ;頂=䟋(2.4]@6f-ThEו~/E'EҠ+rɄ]xY )X>b1}gJ@| P<\4|"bx1DD2HG M4| @B>N}{('tEQv<8:A &пϻ,M[l+0Ѯiܮ.@`˷bԽ$ 9PjTϒ(X-*D1-l7 y. "@I`E@+)(gq"Ҥ4{(.p:vHZ(spהy{y.c$$zV` CK \>! a~!@a}+|T{a"TqdesHYʂFT@!Ĉ@p&D ҁg6 C",' t:eW'|!Drj.*Aؗ琰 \6W0XU\OHuZ¬lpwwߋ4MQ)WXo[nůKDK `DL@)˲jQN8q¨$R6ȅs9g;$qXx1P$S c+e 9Ld'dI 54 $I?Rn,7 nGxK_ /1ٽ>,C"MS|(ċEŏ] 8T |D4jJ9.TrȠ +g[(JV+011[\\9k0>SO=ffPB6/}nJK^"H{(EN"\m)>uA8L$A_?㜰(;ǩJr{7x#~ \./9/>7HT3\|G/~Adn}33066v0!:%"UbY!$p%[w* vb;B Ցs$ ֯7t#n':(@\EƵ~񍯁^*ש>{ŗL^oPdJ< 3m^CRmcrG?u!V+Ɗ1<奘XqpӵB[hW &"L۝,rz>kR(J\T(ʸR)㕯|eܤVMaoU -T*Uԫ5KeSN^.T*CQ\GǟO=z)ǯ$_eH{<^joHmkcAhJ9ON]xZ݉RHJ\<%m*mkf:?p1ܪx}r+vjCVO5yud'jc"q.~GPVQ*P*Y+;Z%/9?0Z; ~8|r4 T(ԭj lfngN{Ν~;նT* B,j{D j%;$$*9ӅT0RS$ 33;wy'.VBZCeh:hصk}>v,ӑ>Z\NpeWF\ERE^BPAƲ111I\9 X###T*žɩ}Qolmq!EP`蹞8?h0& ^* QgDRFRAZE`bbozӛq1:MرYƶ+X ˗c['l6}'mFyhRIP.W122+V`llXlQPQVQP* "0 7Ίj3>9!˘x}.[Źa,ȺK=%ۙ$Q e5kuB\^4?w߄!<1V> \*'MyOc[144ILLL`||˗/(*'{$) DvS* `64|DQDۊ%A` 0}D$W |YM6c)̰+7o|1Je֘akeLi1>> LM&''022RP}<'*Wybkh, Tճ 49:I3\8 2{S_JJd|驩)5k駟y%aµ~~ׅh;h4n1Tp0>+WNb$Xnz44j`ep>LA q"26 駽N}!.l|@3Bo߾o}o㦛nP j ˖-$055qZ\.Ec^[&P_?[9F9Ng2hCD5>ug@lr 0R V>cPJȣVt~|sCᩧ~7nC=Jqp񘜘I2 5P|Өlb \.ZbBjOy:xǰ}sn=s1EQsr\GxR!^ozlNzKVz]33Xrl_N86mBfV\x޸h RIۧ/H,ب/᠗N1:ܑҤBST|9qdIi5t9do@h4_u\wMh(qe]TbDZltzrI`xxr8xfn'h ?j"d%}JYtn3y]&.l#cLVr}rL !ai 8\*_{wlU|lذJ\r>,t:l\< i>>h֮=7?Z- Gu4Nvy4u]7ot03NAt Z dC UʕsY@3_z]QqԑGjA%6mŸ|򗮈 jLOU 38#5|vu~v?9w7voؾ}; hȯ"Mmkʾ\0ݵattofw|ovԓOni&z!*r$_}x^Bz4pp!(bT??@f[r.-: Jn^/N #VjuBkȣ^g+O-&Qg"| M|P~KK;r0vE i_cffF of'\gIt$8*U OYKMkz[A?#,W`*i ո;|(>úu Ve8f=#ϷjJ{YhPRt [>>ہ*egBl8qz zY#5 l6o@Uri!߈-ZF. JE06GsV,_NxtZ@\.V<|C)BhFTFVdAಗfh["!%t|)TUaÄmsN03ڭVe>cchhB|,G>ƿBTl/ŗ54瘊"g0&5΀;(:yUaA}vhRڔ `ɘhKfa־-  Ox"A:"V6o:$ڲ,cF1ё~ ;oۿ QiPZ^J43ױ/0աD-E /Ҍp*ټ|/sb V\Y zP+mU8}kTa8 e[Y3P`k43ɋn(vH@E_rNQHK_Y W} eĝsdg>Ρy{DT8abM4z ɻ {ssIC=rPI,Ml1??9IVg4Rg^zyFrD A5|VTMtI00h%އհE@=] ߈AiKdsgggm\$4:{t#1ZjK ^1pl(} &:]y5 y PvZ}KS(|at [1; A"Y)'{6=sHycP}ƠlfS;6+_ƙ{j4|7ʵeƜHWgͨ"hІz|p7rbԗs~ p3]t) ]h#)^cAZ3߫qt4EuC*6GD FJ<}7LU ;ь5I::}/or&J՚]I!iZ3tWG?=&il @,+Ouy[p葄Pu07;-hN׾pƷ} ;:u!C ölblk0S>#t#Ѡdq=9YJE6G3235>'|شA:ky5qE>7E/>C4!K3LO`a^ZG4zlc?W011O|8㌷ٹ]H㰨l5 ULձ--ع(q`7`s(ufF»u&=ݸ[abpq|;j9Xf5fffpg+09cSaHᮻ[k_jTʥ3& ; !Ɗu}n_Gbn".zxމc_j/~/^/k H>: qQÆ AkƑG"*$<2.rIt>)Ґ63uxG<0s#Dm[l@ @0bqu b!$C4 (z/KdZ?^f5+h4/|[݊SO;;M?ERQ,5 r77myk_K~? QY+2ÛL1ǬYgV_Wv=` ;0<Oo@/. i7W}şyy+g@`0Χ-ctү6lJsUG ޠu3 nI֭[~S 6,㠽^ϗz=z=t]03m뮻v8p6@4KmG.=,(fs 3OTCCu @+/V+Fo).4 biǐq)!:;X5O_.^v *EhZFG|5/_O[?@(!#=&h,_?"@4}+* ̮y] j\?W4H{c;,;$|0Yc~~Yaav>L2dn6*hFݶQ@ӽD{ɰ0Iz=m4 q `Ɗ۽W9^'폻r)M1qz=?t1gpN: $>:PH#2c1 qh4>6B~O0fJF_/̡Z5\'5#؄,jnslw#ǰ`c̱Nx0@c/Ob GƣKd sfQ< 9[y鄯SP DaS9y|ocڃO3i?F M#]q뭷`bbϯ]<Xѿp,@SD$?(Iԓij8!?"4}ҥ(HB^GZ3Ȥ}#n#&-W .pdidWMN d9-2ܠnPVv:t^R#|>;XwܱxիNcV-mQaY0/8'ZeΥs\e}(DNVIL^z_{wl[o1@nM |EGxfRvTJD"Ob-]NR`0d,|wgDv*}_x W+؋dD>xxR"f"#2n@bY@1\K,ףbz4*;6תUKo|_<"P}F2uz ,2 nVI ?{)@gcę(Mw[v<}w:\]'o^/WbI0$e.:PQg^mWT!a2Id*x= 2*>[hwįt^o;V5` gs> f@N oiG3?ƾr(le . ѹ^{䮴4Y\p4ʻxN͹{dA})X(veoK l]g뮻{ :[\liN;i@@oעх7"#IVk_J]&$~]t7h]Anھ(nd`NwӶ??% >.O@HFB(m.oaQtNi쵯{y]0dA24wv[Ljnjڌ1ez= q{9dY^bddKI,˸^wO9 6=ؓe}ٺZ^5Wj毜x۶m&{`PD/ kPiײ̹'x?nV ?9h6Fw:ZsR=c߳pϾ| Pd礂cgFM3Cj 1E2OINLkX-M3eCc)Jub(i1&ZrUpwt_A?wKPrI͸=M(]7-x|?Myٲe[׼xv탁b~Z`ū>5O<*ǣtUW}3 ~`! m[eӳf7 K/78$=$lDR\ըl>g[n݅q1[@RARC4M177mڴk]vٷsh?+MVT$U =xua&t l`xme v{_t1` Zb9Xq8t:Cǡ>iQIENDB`dianara-v1.3.2/images/tray-bg-high.png000664 000764 000764 00000001230 12405111645 017126 0ustar00janjan000000 000000 PNG  IHDR szz_IDATXÍW  zYђ2¢~R<9wos'|ksy2w@ c>IYе2m9`N1k 0!b0` rS#ѼZXÓ@yhyBE+TZB)Z:lBgN,]9NW'|`,@ ՝ =2o& Cmygdmjr9ϋCFA{]4&jƂɒla?l.Y34f1鼾09PƤEkMb3m6 Xi]8"3e1@7޼2ۯV *5a[pUsѵބx-;p&L+&V9$'bM7t+Pj&d`7-K0L kE4Ā3_АҔ&CLmŋI@ [VI+~3V1CMX(Y)d-.?͘#@IgW:ZYx.(f]#y[@I\(3n߸p\hkI㹙h\o&61LL ^e?[U@5,Ԁ\f %y״rhIENDB`dianara-v1.3.2/images/attached-file.png000644 000764 000764 00000023450 12163340053 017342 0ustar00janjan000000 000000 PNG  IHDR>asRGBbKGD pHYs7]7]F]tIME !Xr IDATx}[,yW3=םNHڲ/,@""` >+%T 0BJ|(o7z 1SGF@ -P#L˞ٝ[߻+]53g{m`0;U}Ώ8?Ώ8?ΏQuys>Cϙ)lhA!ɹLhj4x p G#{p{pʕ+Ȳ {{{w^{{{x"<>.^h ;;[[[}looS`4bs]YyB2'5;\!= Ne!^QJFq ԇM9y]HUW=JGtoumu,ZRm40qm@*!O&b!F(t: q`6! sLSF#l qpp !$ 8F)>19?0NV=3I$q0 K !f.rq~SVAy,CH)]H$Ii(d#Y:MSJዦx`'1.e)S)cxͦ4M! e4 cl6pyzh2B\m4B@pY Y^`R$].ˊF zG rlR{r-lnoCPy0cAh9 jX)AAJH YHfS_FPtP2lQurJ )z8RSCsҔs£>`Cc䢶/H5\%<'+ \(]CQ9RkyjC4FDsDpt'2ʑȒ yPch^F?{gA{UO rB$N oVbwwW[ NzZȁcoo{a녀s~x! hrG,(Ip0`<֖EFraadGq||QdW8Q ( GQ!) ߗs PZ0}w ! P$iV@0Ͳ ) MfhL73>I$ wlKIȱN Q̓q(0 CSTaX F677!㺘cs0[$$|^~^Q.M' 0ߗ@e HQ=A0tqCTN9[ϫGAkxꚍF4kgcL$r dS@ `%lYOB) 7Lc܎ #&PƓh+dU}tK xƕqi bvL𨦡Ԁ i i"^jL1醙'MD76%wS骬c"2By<@1g>(i~߿{l>߾n[ s;DP羏}4ah~QWwE&1zQ/Qa_?8GBɤTXaF*A!;$# ISA߁ C2@ fe\^:W\)mll vMvpt:ŋ5FTn# v R&!f<hw:Ԍ BXoZ(BZd@v%qN [)ZaGy F H 8!Rǻm+}Q=+渜#ea(NF 0`䊒MGF*Ֆ8ww`*c,"j:\@ԯ$5l6x ԓAyPq clZeI4~vռH-iu޼0Ck.!d91 DDY U_SjE@k/0z6GL^\wh"ffHN#J%|+KhZ 1xB4 e5ߵԪyZT@߯.vN`FScssF1H AbӨz$It($P O~?gY)GP?(Sq0,u"nž!S݂aק9W,tF$ *n`+YAʽ{6Oh9_Re亮*_9n"\:s%"gεZ#X.gZ諌^~[TXle32kSMPq$=)B^|e1ok0@rY~zIlZKC~_ hZ{|>+|\~Tgs*WBD^p 7Q8DdRd4Mu9>9jǧ%/Gϔ[kR<R"Q!MzPr#"Hf~t=Z0<)RuJF^>HfSIu4Z J`p)cLQN ^yul ]G1lj,U7?,{f@-+`omh;|=w"1Nu'*GvZQ @'~ T:˗/QimssS#`gP!|Dž p8^/]ossTзn($&r xy^Zf4 c6aggt:|>6ZQ9Rn[FP)G<[@} ^Շ'Yz8aA]R3_[R+ U(*q=(riU;iZ+YVw׏ gs(:MSY gXqVs",㜫BҖy ]x{''"^v(9 CloooqdzK4_V+ ?G6J!HM}r&c}*7U)r9#!<0&:h3OcM8jpqn/ A,yNg!|fS/Z뺮(Ҩ_5qfiE6Ȳ Y@ޛy^ X㸤< 4>Ӹ!|&OPRnڑ@GG*tCqf[{^^ 2r+5--]vUܽ* V3K_{2x |_DHf~_WyAOS|l6C D'^Xa{c(3WVJc3aGyַV6|'\ AG} _w G}TG8K-rxQfKK~_%N*;jG^BP>bQ(ۊ7~ !ǝs6%{<2ؤ-+9X+ 2 Cq >64djj kQaGb HoYlFQ^hI TslqZKhc 82HKƸ@)b(y}I9k06mxfQW90\QLJc 61 2cMv]8b&Wa! JuLJ8L\5.B=υm[2jWF%d29#+@n\3Ӗj$.AdMj23z^,@M#rRU(ON)K9Zj>fB*"ǭH$%vOicCq9( T J7P_w]GXF?Zsj0oX/𠺔9c}άˇ$I !Aj*p15>]Z62ols)XGL FᑽVagN!5"nY9 TuG/z:JuKLKũ.݃~|(w}2$c q/ E E E]wDT8#WiOcqg~jc){ZR. K58F.Mr(.)˶a70RfVKGFu>cggG/5sv2AbO.-)W(S`!mнGݏnhC4 .ǻ}yWյ~621h67s8F< t4+o-#,/ l)@RPH ;ɐ0mGy@"B`K ad HdիW t1MraRϑJJ$IRQh*AnN@Jn{T8|ܑϙQ03DZ=dz>ztӔ.smTrV2W0"t64|jJER"li *8-2<Ũ;b\޽{%iQs0C%ヒ_|QnGE:S,}CjS5Wt1,2+-V"7_i%0eZTwnƵkDw5~]lmoc6駟׾]${{: jS26Nq;Ώ]t$n-'aN>+i-hXJvu\v N>5$g|3^CJߚ){9|SZfQ\WIUí삠|"-8Ufo5GiEW=<1Jkg;Ass>#->%ZM-t'o#eӃy 9/ rnQam۹KD&._<'$U <)$^p\@KG>]o6-)26p'uL.'ehgxD0uד/]>[jgWBF6tXu2xy0LcIU^>`>?x'~OkU[><>ߒ6q5\I0rT7 y͓2)LN=)å o1|5kG B[BHysd|rx|d1N K#!<= C_… Bw;oSO}&N)SC2.C]oٹekjc4fWSZ|9h:yGO{Sb| ώFW7?fH}5ic/`0zFyHSA $ӿ{Ͻ +|]?q,w{Wt2Ә9rC*?^я^Q?0ws^V #>۲;m| ^4L&G~Ό>.8_~~/'uQ{̍ #k,7SuI=LKou1IDATxM0ƴqE n)?_s; #rf 3[z_u Mu0x)drdᇡlzo| `|@ϙ*'U?V$bVKL3:`cW ^s04ij0VK| _O[b(ߩ ^8ls Ki {HE9o4E1lV ?d}s/?o3ǧ5 FQbrpo)[9jМVP6h4?dw:go~gFjmFh"ssi]vǩ{uu][0?XH8~_lVA]a@RX\q,'[/J ߆35qmt2* z>$[P;uUzL||OTv7z_ꫯx]<7PxY9* lh3V) IOC*7˲gY&nя~?,_+aO7z7r*Ȳ5Vy6u%8Zy.[_ܹsJ3!3~ `˗GJQl`嫄knPm]a, qM>|z>vZQ5ySd+p'u[LȴJF&8IHR8> 6jpNSU⪑t-#!XXvU@j*}S8EIr<V¯U^c]% #(:"gxja\O{~~SǿV¯U|k-c[d.&% "}do5IX"s1\x{{?_gw{L3`Vll,g/m=}LL ?c ;]_{uWU\)\IHD6!]2 *S  |pO k+54M&^y啗>o `Ŋ~] jx@U^FZXX'׿_reٻh j>D \$j8 _y1N㽦R S,4t:^_KʿUn3h>8rfONzx gM((GhY,E7G$|=ob] OKK+5))_R:S:C+ggoxxllz߽1/9Eڱ7>otJ:( &2!2A;1#(#(;;.I%͈bbqqň>`i0. שּ?PƣգWu|GQC!~CD9#cBgꉝ; 67 " B}mXx7V3}s sC EB;⨹9n !~acgc?.[&_&'vte)BPsCm$L'L/$NzMIœœ[>Gx 򡉐z+u3fBeW~y6bZ=z89mW~!v۽~ QQ?GF":߽Udsί; u]7o>*ݿ /lx k UUē 9wwl8xw;[67{6;lvuпihbۂۂPXBN/fYˬedvv5VO"Vx ~6h^i^X]m/'XшѾ<_i__-bKjj>ncàS^&^&3ߕKg@s"*ogqqv}] [֒ BI$ՁXo2S!*ٞ=1ѭN^'W]\Dr{Zꔐ_ɯ$BS>p5 (!J , =ia| &gu&ib H'qn%G0}a²Ҳad66J,Z6l%ÏKm?\0P;R\P!'pVsy/=ȝ!y0XM 8{)"TS@7gj(kdő@?4Gx7Q  LF1 ׀$j3 eEy`&,]\×pebCEj8'7D( yTTmaGOPh܁lPAၽZM45ki$,;=O< HB'Ӳ&4\Oh&$9bYtߥj66zrF CR_pF  Noσ (rWt]\Ơe\AW}!bMkH ~CO^9e&ba:yjO Mfe@eQ]š 4,:QCXVt⽕Jyn6LL$e0["zTXtSoftwarex+//.NN,H/J6XS\IENDB`dianara-v1.3.2/images/button-share.png000644 000764 000764 00000001461 12261341235 017263 0ustar00janjan000000 000000 PNG  IHDRasBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDATxڍS[Ha=%XR Ө1j %]!#B{*/P=e$BVHaj뺺;;{KߌFbpf.aea#. 60d !љ?ND;|+G$_<n"V01#)Bz3}4SD= 9~>|H?q" :_zpӮ ZL6 MK[f6!hBN\n1WKcf71t鴎v2NחtY˴\+QP P"b<gTu>bt>꼊 N,PIENDB`dianara-v1.3.2/images/button-comment.png000644 000764 000764 00000001506 12261341235 017623 0ustar00janjan000000 000000 PNG  IHDRasRGBbKGD pHYs B(xtIME .-7IDATx}KhTW7d2Ǩ36-(Q|V$h)I+mRЍ .\X]nwXZɢRZMJYDQ0L23w枹!")9&;9Tg2$[GT}PĔ.Xzgm=Q":ɴ*6gM0OAs7W^:Kel(;J)lץ¯Fj h:ʫs uE!w1;~q<>o)#+q{*=aU1)R`J?l`H 0XY8A Y V[ʹ%FiY`{S#>^JRyEZz]˩Of2c0Ǡh[S= H%-Ea$N/{ hs΁{ôʸsL ӛ=yB`-k=]/Waz1Lbe;1@*+g:6|*ѹcE. vg",Z !ei0?lJnenܱk&}d͂|NC`t/E}|V}8l NtPgwX(Ό5TrNfv퇞^%^&YWEq IENDB`dianara-v1.3.2/images/button-post.png000644 000764 000764 00000002372 11472551664 017164 0ustar00janjan000000 000000 PNG  IHDR szzsRGB pHYs B(xtIME6+bKGDzIDATx[hU@/"bAswaw~622rt||ΜyѣGzm/+K% ]*YZfTNGpWVTXTl=P$#H$%b ۭPCn nK̽ \Y`('M촑n׊;TsucY2CMc9CAW4$4#l;nwCیIX6D`8s~cc kP e4#gV_ x>YRohٙN^Ig FeD.^DwR\BhN}nۋ+?ON`MK7]5M3_){K(8j ]@Ѥ rf|029%RLnd$M- :! Z*I@)PvVܬkLIk#d(n%@p=8{|>?" <2߷C&ҏ?`iىP8K˸ybo-: (K(תD]PXo4PTㅿh^|s(BN$zhKpub8Nд4EBX\\DtPVwٴmX&,[hk5jZWb1KUU" Àh4*bdUtזe |&ylvyd7Y%'o/^<`kIENDB`dianara-v1.3.2/images/button-cancel.png000644 000764 000764 00000001520 12261341235 017402 0ustar00janjan000000 000000 PNG  IHDRasBIT|d pHYs:tEXtSoftwarewww.inkscape.org<IDATxڥMHqƟ|:3~jiA u D vB'!(I:DI*iV~E&+NL u鹼&=f ] +\[;wm M싗A^ߖXgc_촃wA ay`P-in>UfCYi޾!X.CQ1&TU8XAƆo 1-)'++>_6Sڝ&EY햤,a< G|mfǀR~)Z@ln131e䪱k[ZDI OOPЄ8m?. [y m+Ʀg-Cnēb0 kAAGMeq  at5h8,466!2 &n'`sr-Ag#2Y !j?$WVpKefvXTUU !ѯ qDJH~#/S̅7HbjSVO7qr$3l4"6@#Xq hK=E}KoocA&8B阋ᚚUNs VIQIK)J1{"؊].F 4IENDB`dianara-v1.3.2/images/attached-image.png000644 000764 000764 00000041725 12261341246 017516 0ustar00janjan000000 000000 PNG  IHDR>asRGBbKGD pHYs7]7]F]tIME  B IDATxwe]{΍CwUn[R˖-rg &"yf" < 7a0~06(d[խVs9g8޺,5Vo6\_u}]_u}]_u}]_u}]_u}]_u}]_u}]_u}]_/cy 8ו5j5踮[ P^W7m8@3UgUaf1_~^;h?W %v~eƀI}s}x,k,PZ@/- <׺RnZoxuxl1QE<` jMXĶmi/\cA)ҚN)$21kߍ5U2Q2=g1YXz׆i_.b"O GE@6,&̴X-Y|5C>m 3+MraHd)}إ=̍dab?\%m0& m4m ܏ĕjczHO{~z6<۰ҖW*H!@$1%/d{.Mj JuCy\㬜=ͻ~? ^߱X-e!!VpmY(02,jMb`}NJy,l `#Pad۸,ahWZcMfhrr `JLd^=u(7c5lN/ Gm!FR!L]Ѡa'?ȯ2?ÑSXڨ0xo*۹ً=%ZET1:C `y1k*]ƄM 1F̯drH!, ),a@e| 1p k-r; 98CYB(nSA~*Lm]#0B`Hhc!X}ʠj1ÿԝ<eOkĭ1Ꝙŕ AŨeEz~ 5֦khUDmP=tV \8Ap3˾B`I)$0Q"ʭ *ױ]p +˺䲚&+ 3V ȻEլ-wq[kK)w==S(QaW,c BnUM._b`̟.OoB&ђU:A.rh%j?Ib".aD (P0B7yE4DB Ē2$(ňV^QVǓXA;WS&W0 - ȂG9lP[^Y[!F\1nۨ( {&ĵۺCH[_ }/# s߈!拧4³|;{qR(@L A- 'ѺѶpk@@+FIDJ-bwi%egUix6ng<2NLƗX N%GJ֖P @fJ/^9vD0Ӵy-qw?l-)Ci:M_sxé/gn?]D2>K'S]iӁ(d02|!7:# e`tϋeۥX0E55N4$,c2L@D~"*f|`|1,YZG-Zt `tmɺ@ 0ڷeXUQ1!F 8L!$+X%kUU4'J̴GE?P j9^Dx:qB1Ĕ<q4'~AloŖ˴ozS[M`l;PV~/nW>e ɉvn wcK'PDcA[@6 U`ĸ8vl{?Db%<ŲUnꄺZSs^s91N"(A8V ]ZNCVp! C>]5 CQlcؽ8"MqׯtlN Hoᱛ_ٌHÁw'ø;L6)UCnҗ0G$[mFYeѮA:ƴ! pi9 K1 Ej`HIˎi- 0bNKk!Ŏ#h'{dsnnhj$s:dGltBΦp~3y*@kV]@prw5&b­bN}}^m^4mM[D eۧ)b<%Z`:@SsY5?H,eYHi!@e02Fhqẕr˭"9㕰2ydqyn-D8E%VHK@pE=onZi\K7d6~+vY E=Ύr+,y0;Tʊh ys92GbLBK2c\ji5q3+NZ(cܴ;Zحȉ۶.b($cX)WXXH[U0Y7kceAlPRFZryg&ֿ[07[l YT!~G R%R@.V#r;b5iN[K35¥LEYBF߇ F"kV|n^]8dq3CeS%(}fWۜ[X\q[Ȭ=d`AB[Na;~v6!T7vm0V]hٕ5qD#"?w{ozwqO~S|pHZhFٶC{tr٦l_8Sy!_G 2D#9y]̐(9f9Lɹ+fB&6-ɠd hYNt1$WI6t-]l%̶v)f3cw|gr4Oԉ<|y^1 P0JdUsLxȢr fAkı|Z)X2R Z`(]<1]p9e/*1&[pfI'( AIJ(KGXO@׈ސIH|1m]hj z܈_|8ᩝIHG~QVY|:fqCödL'cE0Fdi [-v_}.eׂSje},3KHc;6&VTO `t,X[jaF(/j @L~faŀ2k"_Q8bh%в_HŶiYȴ Ҳ XrDųPZvhe 2 <2`r}.|beĆ KC붢l)$5&qc-jqb ,$9E~l gQvB"ZfcKɏTs2\ςڊ0(,M_I,% J[1+v9Yt.P!u0§ڴT k6n__w(k @iр06&_P"w.w翅W x {!61>/Fᗦ0  Hᔡ԰@E/Hc %Ӽ<IReaBJҘfD5؞YE <, M.kEb?}xla}jkxVض9 ŧmc|!%|&z0W 7MLPFx5xq@Qڅi^T-tF#ۊL't:!.ʲ869fbLHDZDO%[I;jG!pmYȬ3$) YI4rJۣkm7cW@77#6f-P Ai7Rشьu,fu(na8l<'TV5RH|܅BfNP2aOF6MקX4e + b|/&Cz=^$nvNj!a7VƁQvZ@FX͘++>*-Ĵvd.jAn\[]R]4v1 䏝pɈ'S3ؕpž?$ѻ/ itzP)-e ,$VUK`)V6f|!_܎p+".j҅l]}mA;f[. $[L*~Ƥk#X&˨fRJ 1 d80"yay\LREBIʥɗ p śFQj%DK"*6^ dm0mG -le/(ZY81-Wq bAM8#1#)-% 6Ͱn* ['%mؙ|b!ޓ9۱ו \Lϳ=?p0g.{ML^CDjfhw2ijieF#eKvĠ[H0Lu,|C.I7#$DQ3p!ϼ噫Q̋B2R"p݃ӮS^X[4deZXz MiXUp(MX%li#l Q0ƈ̑:D$JE8gXb!0HH"6R81Hwbqf;W¥dOa ZǼ)n>8Bm V;s6^Ssu,{W tMDM%l\7sϩ0Pw&Y1ܷ/;\0*R&/vkX{&\nkgpniuZ5$!H"&FCP6ۄ  %@88'уV{;K,p. NboVBQ3^ 2t,,Q|îb u=g{[|#ځg =+яMH˦ ٜI=Lehxه #[)<\f4/ЫcR`<;9Y]6q܂,:m"_kE^0Hb"@Dd2eQpnq'mR-Ǭ1McvҦ-)DwOx}S\4OuNÇ,.'.܃~_b~xd$(^`W)8wÍ;^-l#:hzPm?AdC  wXhQiMPȰ-;uʼnO%ar.{qq-n Z)p yrv#Gĵ" s3ǸXi39&Y&nK*15 R,(泈<yPŊ,#T2%" |"'syDTVM#<1 7?EGLs]riٯ䆃SH)x7!!+:eoS&b=b)&ã Wq Zn&q3'``]˴utn2`4$x:d 9ʖl3KTei4 g5&A||l?xr坣8N[SڴCV-\6|d'uKfNeē̍ 1&ч>̤:j<Ε]oy NU gfuKxp$d`=߆\m/Ix4zl[ J=c-r{>JR,`=2ӻ(źFEͼJB#$?AhqJNbnJ X>2?vt#Tgd&9TL(* {I^|`wZ?Wdxr7ՕZ FGvBENf342J AkVtBN0t~ c46CZQX<`~)&-n%uYVXzWc֮WǘmqsQŠCcg2S!^27z; ~+M(|#ĺ` b6 }N:-#7Z1)x=1Px>710K ]r FZ6A6Oqp\+4e,ǣ84.&fxb1,/JL>itZ=}ct N*szҲ*Lo6#չݿH&0_٧ /N3/ _`,'i,i>_go݋L'J(6FXv&N9CP -R"\F'"~S_fZs=3O2 %ٱWnG %KheC4wE0ҪV  PVh5n8{1Aq)R)z&׽b:t[irp:&D퐑!Sr,5T IDATɹ1)?3_}7\!O\?F?Zi[ѿ$v0N#ZZ'!t\rc&s/m(Vh;soУ}{aOAYW7K+eNSamcle cgn Ab) ft:zXncPЀx$=¨qDb-ǸDe48[|>GС!?}Kd.gvfo^@0wNr#5N'8150}y_XxJ"UFnQ\9nO}Uc ?s/R9n>zjyflP Y9q#KǮ_l~۵ګ;DB^?@&&A$4뢔%y*}{8q):q Ui!YCyujz;c.]Fr{;NgV>Į1G. ŠCLl0"",]/=}7r8s ˉjˑTѸ >KG ϻk<Kj% :\S # )tF@d L$Z:MڋgQsQ˸EɗY:ll4`͖>۱w2ˁ3 {D 50o~y{N*q쁯Fp'WXL+W9qe%h0hu^ GQyoQoG|ro76Ml]W#QVE_fYyHpS{g%;Jxjk͵xՠLUs/xp'#;XxVJɛC&%Db/JD%EVz=uƐjlϬ'݈bpHmj]!z/ a,'ػsIlǝ46wxiZь!/оaHF $l59y89x\G0cFӇqc5HxĝL6ý;]xOЎ:HZ<3iվr+ڀFʗarPX:s˟#귢tBV4Vۤ: 锯`/[&H&x!;8"&N- . ,i6q89Z`Sm1g DJ+X_&?2I;en 3q[|I~'{sTuZ7g8R0%v#n$Z4Z#7d`Uŧ>DXLk8<7e"_Bj|%fO>ɃWmɿyV=2qLF> _Ŗa!nU籢*hjF48Ҽizѽ>uwLw機]L=H,z /7N0+[iS.܊*awy۝E*s AcyO}rxBXQ bGhPB-cpja湿5_|eȷ$6z/^? Ƨvd# 9l~ hq jn]" 2&2P5)[TR0X%NtP*Aճ/HHfhEԪS֨6i6DJa f+4Z!{&KSa?-M"m2Q=`6"_cs_7pg;M1s2e=џuVZ3f|K<"߂e'q/ܷ̒8BC7kÂ(|E$-T{ y"j'#pj4n[!W?cJčsea8v:4yL1J"l0iWJU|My[#Lni剃12Od,HDDJԔXPN! Jn)E70eIXOOه_ap: {2{m*,vv,6ܺo)R '堍Ñ/2,> [|J9Ks+x!*w(͓u?N܈==TܥdPZP%g?e)elA(Je_jb4\dp1Fv)B  MM߄ )LZ= zgm D Ȕٮ(bq$w _ı$4˧~=|㓟agsm' v+OOqG(lv)\D"KRWӏ3Yeu 1>=EK 8`4F=Nȑsl`4ST Jw!#drH74Rfݞvh߆CgD!vs( lBƵ,/h@tqGP"O\lr@`M%ً퐝"lrƄM^?+3\eR {&}N^Xd! 26<>+<-puof\,ybR<{r9/(N RVY(˳KhF-fshtK);UafzFd4nIxߠ 0X޻X -en*cRjgma|36OJf>1BiN |drЊ -TdAG 0̦QgH䐎ӗ+28lX$Z_2\bm *3НS[ /qtP>a_cx0kgN#ѩAJ:|'\zlU[r"1n]]lkz! h@"A8 8I!1:3>̽;q4G,3|9gVJ`=\VY $adVk9 &aHh̸<%@mzA6Bdx$Zس[⪔ߵ, {'gUZBXV0 _Ep! [ U;&v$@,urC?x@q [8Hc2нH]T>sҦ $3i m@i]H `5kX;ř[eb.b~K -!=4եyA-ʷ\eP6Yj ?TVe?қ[hvrNFfwދ&tSyXho!W(!e SwBZv `}՗8^ɷƭaG^nTn߆Nu/Y.MÆddy`u |Ŧs]bC2w>B-bX}O|a 6-c6(ifR#HF9&&ghZhZ˛oKx؀;W1/ ={Ԁ(ң\OQ/ ]vx0DO`@othBł,~݌fC;Օ0XX$` `}v c6vaue?MU z}_*~*7a=':KJ !"DO QkA,|Ð .Cy=y*j9Uɰ2JqV6ؔ=IDATM?۷oߑGgTʿ&\;Jo 4=fĜ~X_PhdK("q_asoYž &8e8v~)X&Ul4j Vv~wOϜ9sB[7+ϿX ʝuHY?>P,g/0C_S3uw$H!|uZpb={o'cbf BlD 8ǿIҪz/..RdR @oҪ[ҏ~h"O=|̋.F=OC5lZ@SR i%Iih.H(R˭)@$G C8HwhTyy=sHz]&=,&+KK0L B6e4R1bLcä"RCx (<4@Ws$j!IGBpl !8.wб:h:('/{^g8o! `Gpz0`F m7)B"F%yo TKdSBJ8ذmVw9,n8V<=O8}˕&ys,50DtKoBН)暼BR1uP%R~5W~@ݾK`K%o4BQlRqNz}iqaq~quvs˼1M,*XY\ mx 8AZ}^>ӹ su-ip'Xb%Xb%Xb%Xb%Xb%Xb%Xb%XAeҺүIENDB`dianara-v1.3.2/images/log.png000644 000764 000764 00000003421 12261341225 015426 0ustar00janjan000000 000000 PNG  IHDR szzsRGBbKGD pHYs^tIME 4 IDATXýWKh\;=, V"m&I֯P(P;d Ĥ{Q)!x@ ҝ8Y<<=9_wf4zd3 cpp7;w]x+he _/,##G+v!i F&Z}M;xmr -MH8ؿqDpDM @@P$#-Fޘ>oaG" 8K"u!"xöd[B#"p/uXWAʪ%ѱ5 pm̼:ZXЉANo/h>k՞[Xk-^@-GIE$;: e?p"h݊)E˭7 YgWD8;m%H$ Su6em4ZE"RҌ6lB 4#JhzHkD4k(9O0p`tr 63%r57$eݰU(IKIЋ?֊h0 ka&g 4ZS+E ؖ6Vw#oŮ&d gBВnQ x,x<ZE>hm<[$ڕ5* nݺd 2O/㷧'pa)#,L'~?1< \|wQ,TA+c*য়#G$!Ij9q4]I{qzz01~2Ԛ%ϣW(o$Hg}SNNhAg%&''QVQV1;7|ROZuRt{hiFIr|tj7ocX(hիWĩ= ݻfG*51<<.<T PSg"^/O^L㘽p\gL4K9H @Tb.L<<Զ1w_7ϟJ[ǡ:-@@n1ve y b+,ҋzac$ɒqu~~xt(Fd0DOO bÿO| oDd|s`){?˓SIENDB`dianara-v1.3.2/images/button-parent.png000644 000764 000764 00000000642 11472551664 017466 0ustar00janjan000000 000000 PNG  IHDR(-SsBITO pHYs:tEXtSoftwarewww.inkscape.org<~PLTEU]Rϫ\QId@ˢ8{0tǘZcԌRЁI8zAIIRR[]iuׁ tRNS  "()+;^gt@jIDAT}( *%8Ǵ$M|K뮫{70Nďc>t]ctt-.h^#N/aŗPS\ C';GG].tC#L>B }u'~~et:͊%iig'W%(;T2tXrY HuΞˀ V5 b,0:1;uo ,BV yH'6g9.y*z_Yz`z Xp@!7~}?.OjxmT9Oeb;U%tr Y @W'P#{x'o'WL^k9U, e.T(CW_ۃ[ͥ??KP@4lf(Ot:)u L^;d`PaӞd2k?౯8!Km L܄o$cx*# ؼ}wOm2>h&|N'UUU444`(mw$u$'q3D*ggދD7q]i5F!-/½^/pAY`&;=wE >>2@_eIjst=7;qݴ(K%8c][ʎ{CT"nD,!:⋨D<ƋG lJu-Pߺ`}V+͜C<ұkwn̙3S)@cSB ,vSݶ Nn2%a@iSm|%d3Ύ/|E4۩,yn, "H&rϟW$ yv`R, IENDB`dianara-v1.3.2/images/button-delete.png000644 000764 000764 00000001200 12261341235 017412 0ustar00janjan000000 000000 PNG  IHDR(-SsBITO pHYs:tEXtSoftwarewww.inkscape.org<PLTEnmj++$xuJI`]4*G?!#$'1&+J?J@LCVL#!#!>4C:j#j# {<2Y![!93 33MH::92  >>D?NJFF VVWTe`b^`]caha/*0,2-2.sqmmvu鍉cA!4tRNS222HHHHHHIIIJKOPQRnbIDATE("vwwbbwϡt |dGBb^)pܬhq/yR4vFƟ:|X,t{8L\j "˕ s̲;y_CkfdIENDB`dianara-v1.3.2/images/menu-refresh.png000644 000764 000764 00000004206 11472551664 017264 0ustar00janjan000000 000000 PNG  IHDR szzsBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDATxŗ{Wǿy}߽w(`Dj"?4!&H4 b J4!XRbe7sg̜s;eY|ɽ}{~wNH)񿈀;m5|*o\[X\R\nKBػ7"?_5n dIg;/^^ѻNX0%Jc8<^>r=d^d;ݷ9k箽{5uM}eU>UJpqGJ'ؼn qĖnq} U j 9+6c_[`0$7:Ѕs-g&nun^яTq((1048(d s,vb^aJ2u-8Hծs%\K(Gw(\fѕB,11 l23WT!0I(vd7/OE-&mKN xK'XNʬ8PoQcC: 6:[0M] 9q <|@J22 ?Ly[ S =3b@d?n;/&t A^cwFj^N+qeFz'369005EpT9(`sdΫo9f$ h$FPlP zbӑr9}ЬjƍGP QU@҃FXr/G}8jء"hG6j$-H,nCa):D*qƢC;C"₰'@4fm( i,d>)ja2:NA M ^$5 6}Dm]?-x賔V G(6` F.0O6q,EvLyUNiW9Lnpld;n9*3+~%s{"wEng*iR~[NbD@Hj Hb< V(` hӸV)G8ԻVP #efXFB!\!!ՀZj+Ca`x$Tl_M<6)0JSCP)wilrs_7^l6PIl_mVZPՅf\$()BD,YAUbp 'RZ^ VvTiQr 1MҤ!n })@6a\\T\aBD\2I }0!ߩY>)'9 RF)Pc 둥!Q].ndnIEh.ڟ 9m .-| yL\Y ^/tl~#ƋSS#0B]3DKk`R M󯗤#ɧ790r23-)oq-: B2tAyVO,R|NQ`3 @v)I0JbE#M; &4a٘- KCŋ͈LB=rY' W>3Gkӛ62d&VtIrpA=F"D4h:Lja|:$_t:ڃiA*˸8;e[8( +59G>!y$qc0Ma@uѓ$ުH42mJ 0mf[@|5 VA^Q<@R 4 PO\IENDB`dianara-v1.3.2/images/button-download.png000644 000764 000764 00000001572 12261341235 017773 0ustar00janjan000000 000000 PNG  IHDRasRGBbKGD pHYs B(xtIME '6fcIDATxڝKhy?35$;6mngVwa CWªU=*O,ԋꣴahiK! ؚ$$]AK̰WuX?=plHr.8:?-9X #g'$!Ya~_SgРIrи֍Gm sN\"۴v߮柼To+%RfG)Do%ZO5FG6Ut"\nio]nhZL HPHh7? !Ed"cPP)_<}69QѲ9$PT8-dg!㏃`qpF߾mX!d<,GtBqa`0[x,Жx0J(e zga "ɛ & X#ƠF@K ؿ|T&f늱aV@WiUe CRL㽔žt?m"[TՂ0#;Z0K[l,%Ko*zw/}a EL.ʲgA 9Tͨ$4 y]V2"|"aN">6",ea:TR@C/hFi4ZFEFәŻ; 4#+56~guY,ܓχs`b$x#J's>ͭcY {  iC!D4~b7t8範V$8[ÖUta*Y{p暫u s|3ٍjuIENDB`dianara-v1.3.2/images/tray-bg-low.png000664 000764 000764 00000001165 12405111523 017012 0ustar00janjan000000 000000 PNG  IHDR szz1^/dnf1=!Q) c@ƅ pM\yn@Wed 5Dܒr 𝔈U)nf?PIp( JsIB, $spVkI VEMYqsd}{Bp p* 'A'!r'e?Nz\ unc';6j`$H ^nk_~?IL9D5ʹ~&Qף*4?~(`] 'f3>X2v0̾/NeHaS p8l<(C^x?B,ۖpvh@' 6qEC4\Kno5Aؔb1gz$s`$3UQv"HXrIENDB`dianara-v1.3.2/images/button-configure.png000644 000764 000764 00000002652 11472551664 020161 0ustar00janjan000000 000000 PNG  IHDR szzsRGB pHYs^tIME)%FUbKGD*IDATxڽV[PSW5h"be4"2EBCx"Ũ!!ommVU:-XEZ|ؖ)Y?3k}]{a#\]gX+Tx+ߴ;_[{~6c wX]ߏALxxNdɉZVr{zLx^PkT֕exl%0j^S]x%hA%pX+˷ogx\ֻg|dm~DUH^~?NY,cPyF7}ϋ=\il*R+֌&aUm<R{kw?K&^C孺sƝϻIG; U؜ۛ-?XL}4g 5]Pռpb9 ^fZi91NHJ8%zދ|QW_gNn(; V3D{6ZDeuo?3g0w\D?6T|Yj6B:u-tK LH0W=vMFgY.򱯩9`2 hE a}ޭ5DD"kx[P^(b gJ3p8D[EIpVwOn"x׏44DigƲbQCG܎Gq#k~/q* #0m4T@&˿FX%%ӕ?]j%asj}fɢ%* ˅Bxzy".>e.zp<ٰٔqR S ((sppHa<+ ާǏ6bBD vvv* =+~ya 77BJj D$i^{Ξ'$V:h2 3T b"߅ȊD\7> r]ZT`Qkr7 t|V$$&@* "CA5-2\TkG0`^$ 'Px1[TʷhEѱSJھ}!Q{mmmAwG_:4ǩץBG?4Gv/<4\_22:cbm$Īji#M9ގe0NH sEBdkeþC)r!02w;=ol2M(Deo~/I&b|y'=A"k#HֹuvcWYt$P!ʖۍyÁ_Cr{y'1ZՖ÷+ԄIX~ԗ#᱕;%r)! kuKVɝqJ,;[ZAܭMblO-<@1m~( ap{hr&{(PPFjr(az'Zh@Q',>W:jTEJ( G, ulҦ yt G5FE Epkb3 GWzv4h ;}N[9FuE pkIlǢtPiǨ*h wÈ+@u}ϪTp 'ج6T;+,%kd`c#G^CEiRP&dopy i &Φ +sy% !JVUbYE pnnIE_L 2J*F[$vEUW{1W3bryWC@4Ҍfr,r\ 2kIENDB`dianara-v1.3.2/images/feed-clock.png000644 000764 000764 00000003655 11472551664 016667 0ustar00janjan000000 000000 PNG  IHDR szzsRGB pHYs B(xtIMEGnbKGD-IDATxڵYlUUH8F} I1*IHL Zh`@d(tk \:{;OzaV*"䞽kڧΓG!!www W<{SSSgggYsGpoc kq__ʡ΁`nLJwW4qoc k'"',l ]7; Zm-I-[d۶mr p$''KBBDo[n5iҮk^03/]zb[+%9%Y"##B\J__tttHRR`~)*.͛7KY)`&Jy /[3<ٴi vOOrMjQR|aQ<B#FN>Mtvt6޽[~?XD$ 7'{pbRb n'##cЈT=(rF!&zT. 3w!XO(Jj170 (6p;cHN5oQQ l~`‹%i>^LD!D: ]P5p̱g*{eŕs+`LQkaXU ?#ΌHV.8𣯶F`H䊹M 47 VupedH!W^cǏKllb p9 آ2 pmvjR*F9%s|QDޙ3gL]5_ \B+K_onni]V.'+5a;-Ck^sSN Lpe+:ӺmϩCњfBImm-O-nMig|񴴹3 L|ʕtuuՐA=iq >NIIbʒ)MHMMDB} ` c۳K? ~;ݰo~"c|>j {[UUyB{.fummSv#L]Osc k^0`/mܰbnuq5e?R׮]~oWsyAG.IENDB`dianara-v1.3.2/images/button-busy.png000644 000764 000764 00000002721 11472551664 017157 0ustar00janjan000000 000000 PNG  IHDR szzsBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<NIDATxڭKGOUȐa׸!aY$JU4d8~~Qpb d2""Dfu*Pݝ&0ނWWw9U{u11u|l#md!=!?OUc=u ۏAǰaD$C˜)_K~ 'ǝmMj~[;uL:b6"I8$ɭ[{-Yz5eVNDB`4M `~mpV|NX8Kى]063 'ov$DuX2? iHKҎ _a~{ZE`4Ygca>b|g><@2OKb9Ƙ7lٱ,qF%VL@ (n^腯c:8+cm JF"Me$H9f00qyQj|Njܿ-7juZOAQL>Dl'MGRjx g>a.-B҉I/1CPWnyǏ1pUGx&.bCh`i5Ұ6_4lqP~||LnےU`f9\ɒ'>P,&COQ^';H;̄$D[hS.>ڱ' ЮiDZ͢VadmJ ,:>Q=Q_\x_J? l& ӈK=Ā] K^HX:bt1V2tՑf)%bdßf41_2b l 3vf{Z4ǩ1k~ |->*7IENDB`dianara-v1.3.2/images/button-offline.png000644 000764 000764 00000002137 11472551664 017620 0ustar00janjan000000 000000 PNG  IHDR ssBITUF pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDATx}k\U?8o2&Q4LTLIuTXJEq7Bq#(d7 M0h-7bRk"P4i27y];Ƒ/37|?ܹc9=i32%xu |vL22"8QL\aM]XmA:nNt=e}R*cunI#Jgl2U9_8KɕV;૰xǐ(:n&g7?勧8v!|G;U`ѹ3Op-SR"w1bEŅa2[ +!<,wqM$rlIc 72|G༮|,O1gdq]a7@-rɗz EA dC'53ϑ߸a?)(+M;~>BHwۢegx ed6{ȷ8j6NZ^ ȊvHY/uF(oDP}ۍ2 bwXf~mHx58ʃT=f CUcԮ#?7ujlc|@ڒbm?:J;Dh"Rő I~7h,eh7Kb(r2[dxy戨H9ɨb4enkiclYBW})X%j2Lza]L*5KHyŧPOR;79 I5=欗$, *&!I* Ӑ$ 쑬[BLՅAb+Nb; T5/y9Qn͆e5; iགྷ4K } En>DXrlTLrfv/V%kVWx jolwao^n9ЖPW DJhhiB?-Dў@ s@#c#Kt  ,Q,˵eQBxzUV&t:%3= ڮrQ`ѕE>m ȘtkH?Q`p HiQ0F > ɽ٤fr)#?f8IЋՃ0^Sz:;\2͍&Y&0t!!, %[d/+}x'׍  <.İ0N"D| ǃ> 07y9M1 'I9L=ڙAWN{s(~W4Bn:yaLtc뉴N):FcJ8XZbFXLb! Εم;b >'ow, D +AdkJ]UfVZaV񔥽(zsbnZ-9>Vg:z0J0@JHCko=;Os7a+ݠڑVd5hAKV5Brq->?BN} ;GtuaF/HQnIc$ ] ifmݔZ&)W~^˴[U(ӑ"ZoV)OIZrn4~<;]P9 |.za<97h]+Ǻ'G~U3K3>k[ˎ[Z%_(Mpc\͏$;=oS-SoYAIY{ffnQM _:XDBеaW_q:JJJz)**B$X5S)ةd%XcU3v6Llv^\&HI3hV3/xJLܒNܒNZmč9u;/:FG3 $]設E_Q_xi }w8_/M'MHoohh@?P MMMևfh݄jZ[[1Ќ~(30 /^`(ZD/|C к 9srȆ{=+!bx} V~IENDB`dianara-v1.3.2/images/image-missing.png000644 000764 000764 00000005133 11472551664 017415 0ustar00janjan000000 000000 PNG  IHDR00WsBIT|d pHYs11(RtEXtSoftwarewww.inkscape.org< IDATx͘ Ts7@eXwK(V0T hcm֚B"U5iմ&mhU*f++² ˮa5;;gN4lNؕi—r3s};˶m@70. ԅOֲ|^:a&lIe@?nr,9<˩={::R^/pڵTLKq{< Dl$2ۀHdY;cR0 ,"/}e^zPc# P([1:z?or0Ɯo݀2+E7H eߛoǙ6}:nɚssW z!%nre  CW$7R,Z)qY"m qȚ)bljGn_!9~_^[WGټ:lK>/zLv20Ɂm?zQ=I*5%m .Y"#ڰLRjM߽;ޏߜ;.gX`|6W^{_x ə潤ʇTд"/ݔ sI>wWYy-x1h;"b gf)nF^ c,dycXfJt\6a㪞Iɝd7nT== V24~‚_HYY~``Gw0{ HF99َב5TF R&bd@#3#c80u]V~5Ayòe-.Fn;? p//ӷbN_E)b-?%I*5*J|ӎQ_#f;nH$Ekݶ t~XA@3 g@.dv܃$0 + XVJcm[74e21qZ8=䴈Oe#6dxӽ!bmK޼ꞏKN 2t7OPUU8܌.{ \j@7ӊI4 ?_;[ke4^ 8N)!ēgώQjI8\bm!PU]n7׃=p6ŽOyIfnGSxx 80/Dߒ lzj ;q2r-duW!ba0s,RFUs:GrT>}cT6%D^C_B{%H bFu"X#%d˼t< kn!ԗ[Ϸ~z x˲$?uag/cÂK&70^@!+|%t&FlG_ >FetϽ] mTT}o=p/_)V\n Ǥ4ɷƛ U.n!{q)MoK3.ZCd 3z@Q92'sq%=>O/t"c!Wt7=V)9YtN{SOau`y/,+ 䣮d|k@8֯Xh,MY . H{ "|N^ԾpJSWH{#;Y+}߳W'ZҰe,^p13DRA|vV "Ff \͵}uIF ѓ5։>}4-K(N\N'5W暵koeg1MH$R83`A!r99ru˖-;`@?wtc2b^aV)m5D'Iy\~=,H?.ap NNUU y$NUPO8[>zPMSWW@ Plcwyga hO@τ EJ PXcch8T^&NBsADYw(~b6ʧn*:! ~GGG6i")J6ڑD,ʃ6m_j n޼y;~*@F7CxN+[gkϞ=}1 ܙ)w&x覀3@70S@]c9ջP4h, iQ/_P[%+IENDB`dianara-v1.3.2/images/feed-inbox.png000644 000764 000764 00000001541 11472551664 016703 0ustar00janjan000000 000000 PNG  IHDR DsBITO pHYsvv}ՂtEXtSoftwarewww.inkscape.org<YPLTE!!!''''''dddnnn~~~ɛ<<<@@@DDD]]]{{{ۄƕ˘ȟѣ֥ܫدⷷܺټ(tRNS237>?JMrsO"FIDATxWaqI(L}'K%dOhB 3Y˞ y,W|]7,P @@hʰA {pbX P>2tPcA6lTtRNS %&(.//012<=@@ACCEKKMNOOOQRRTVWZ[]^^cfoprwgwIDATxmOA<.M+V!$ A1u01zxCē"mŒJKi}̎]PeO旙A/D/zm. "c[jHwj_MQgf@\RÀ}ΚҊ)!}6& 1FR!~_éV 2Xa>\jj\Y wf3{=2lo.@RI'V Xa%`C>+ߦbZO6@gzY@yꝘnw>OYضKz8݁:1 RD%l=z8%d&2_D͉x_"p-Tm `W?wj,.}O*Ow;P1' 2 "_?v;y9Y o O*c; G HA#@>z%d "߽*9xQ$T#X^|ƈĚ!C05 z+ A'.rJq\U# BYrv+Ýv6-2hX ) a?pDEnZfc _ *IENDB`dianara-v1.3.2/images/button-close.png000644 000764 000764 00000001455 12573333661 017303 0ustar00janjan000000 000000 PNG  IHDRabKGD pHYs B(xtIME IDATxe[hήlGjc9-.EXJ22ɱ( ||H  XZLnvss m~}DC}1qj 7ZwMc12l.w~`K߷n߯ʊ+gθ6223Gx\lK&>X..TJKK%=믱%Oj zYanJG"8$8bMy椚mu:f.37#;;{-MNHK{4GtԷόZҾi[٧ic]Ⴋ5LO),WD\qO5AZ JAtW?̪j̊R*ZuUJK,Sz|@·;zUj&RC 1 9&BVsO30u.~uTm2Cfw))X8ZE&>K%W?v#jSvv@3yw)n)buYWH_d%@oЄe.|QQ`[~1|Ɋt]Q"Qd?8ɛ 6 @z#̔Y@%Z[QIENDB`dianara-v1.3.2/images/button-next.png000644 000764 000764 00000002701 11472551664 017151 0ustar00janjan000000 000000 PNG  IHDR szz pHYs^tIME (SpbKGDNIDATxW[lTU]>fL[F&Tc!%H D( j@HI%BˣB}@[yi-ahX5LrkLwq;8~q]Coe$ "Rm5=g'yOAXaK$ǎog7 ^ $QlfO;> F 4\E1sCXd)DOxn9.JuÆa3#W)+cV%<u}!0Q6{򴂸qb[ܮ7gIOM_GM-lb,;eQݓ[Qr|Hձs%j.&8!Ck՟}+DP|/J]Uvi 귗TVwW#t QEGXB\J-"2gs?vx+me1䑗e vX ˲` ӄa4X>SSEj/dp!=[\}aM.*)' @Ȉ S&{({3~37\cGx|JjN"89q $2Y\P (&Qd`Lл% r]=E@gh΋*'C]رG fhDa , Zڐdaw͆V38} L% 1ȌTpL KxGcP<DiZ&,SAr,𸉜ǐ_b1ALڐ,n߶'YмaM L5pAAPI& ]z#UomؖO5{A"K$AfMtyO :=o{{uCͳj$k L'+nhO_=]sxݬ-,w2J:`tkadtI>g۷clz; RIENDB`dianara-v1.3.2/images/menu-find.png000644 000764 000764 00000003251 11472551664 016545 0ustar00janjan000000 000000 PNG  IHDR szzsBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<&IDATxVYLTgFVPYgf}o0ðZW(-j["uTmjmҤOKCj]7ԴE[ܹs; bu %xW* ~¨&N޹+ؽi3I^&瑩`xx"@[`aB.B@a_k5ϧ%%AQlEl,aaP(g lV+$P*P?w"!ua*H%׷EBB-z`"2,G2;`6t8iw@&!|۴4 $Ȉ/s:7lD{[st8~G`ۂ ۉR4ÝlFa^n"1 l6<**""zFNj\tzd9hjlĖ>y ْ)2~UeՃK1o7p1 l0vը5-f4X 7I0-Гطg/.;+_F~~ϔKQ?8>U,S2DORB ȭQkaQW'pYTσs U܂::1xBCC/=C,vn pwWgVlb?4*5 ij"29pr{I˖VnCT?\" #yp9!Daa)|J`ܸqo ?5b|\<"##Ke8%*b& QD-H2 rW6lEn݊ヌWzں_~&d0̏҂\s!#p0S`C´;fTUIMIhf?w|^zE.5im Auto-update Timelines option. - If you connect to the Internet through a proxy, you can set it up in the program settings. - Dianara offers a D-Bus interface that allows some control from other applications. The interface is at org.nongnu.dianara, and you can access it with tools such as qdbus or dbus-send. It offers methods like 'toggle' and 'post'. .SH COMMAND LINE PARAMETERS - You can use the \-\-config (or \-c) command line parameter to specify that a different configuration file be used. For example, if you run "dianara \--config myotheraccount", the config file in use will be "Dianara_myotheraccount.conf" instead of the usual "Dianara.conf". This way, you can switch between different accounts easily. You can even run two instances of Dianara at the same time. You can use any name you wish to identify the configuration, but it must be one word, no spaces. - Use the \-\-debug (or \-d) command line parameter to have detailed debugging information on what the program is doing. - If your server does not support HTTPS, you can use the \--nohttps parameter. - If you need to connect to a server with a invalid SSL configuration, such as a self-signed certificate or an expired certificate, you can use the \--ignoresslerrors parameter. This is not recommended. .SH AUTHOR Dianara was written by JanKusanagi .SH BUG REPORTS You can look for information on known bugs and report any new ones you find at the issue tracker: https://gitlab.com/dianara/dianara-dev/issues .PP This manual page was written by JanKusanagi. dianara-v1.3.2/packaging/000755 000764 000764 00000000000 12607735617 014636 5ustar00janjan000000 000000 dianara-v1.3.2/packaging/mga-mdv-rpm/000755 000764 000764 00000000000 12202277507 016750 5ustar00janjan000000 000000 dianara-v1.3.2/packaging/mga-mdv-rpm/dianara.spec000644 000764 000764 00000002433 12202277507 021225 0ustar00janjan000000 000000 Summary: pump.io social network client Name: dianara Version: 0.9 Release: %mkrel 1 License: GPLv2+ Group: Networking/News URL: http://jancoding.wordpress.com/dianara/ Source0: http://qt-apps.org/CONTENT/content-files/148103-dianara-v%{version}.tar.gz BuildRequires: libqt4-devel BuildRequires: qjson-devel BuildRequires: libqoauth-devel BuildRequires: imagemagick Requires: qt4-common Requires: libqjson Requires: libqoauth Requires: qca2-plugin-openssl %description Dianara is a pump.io client, a desktop application for GNU/linux that allows users to manage their Pump.io social networking accounts without the need to use a web browser. %prep %setup -qn %{name}-v%{version} %build qmake %make %install %define apps %{_datadir}/applications/ %define pixmaps %{_datadir}/pixmaps/ %define locale %{_datadir}/%{name}/locale/ rm -rf %{buildroot} mkdir -p %{buildroot}%{_bindir}/ cp -p %{name} %{buildroot}%{_bindir}/ mkdir -p %{buildroot}%{apps} cp -p %{name}.desktop %{buildroot}%{apps} mkdir -p %{buildroot}%{pixmaps} cp -p icon/64x64/%{name}.png %{buildroot}%{pixmaps}%{name}.png mkdir -p %{buildroot}%{locale} cp -p translations/*.qm %{buildroot}%{locale} %files %doc CHANGELOG LICENSE README INSTALL BUGS TODO %{_bindir}/%{name} %{apps}%{name}.desktop %{pixmaps}%{name}.png %{locale}*.qm dianara-v1.3.2/packaging/fedora-rpm/000755 000764 000764 00000000000 12573333701 016660 5ustar00janjan000000 000000 dianara-v1.3.2/packaging/fedora-rpm/dianara.spec000644 000764 000764 00000003114 12573333701 021132 0ustar00janjan000000 000000 Name: dianara Version: 1.3.1 Release: 1%{?dist} Summary: Pump.io social network client License: GPLv2+ # Group: Networking/News URL: http://jancoding.wordpress.com/dianara/ Source0: http://qt-apps.org/CONTENT/content-files/148103-dianara-v%{version}.tar.gz BuildRequires: qt-devel BuildRequires: qjson-devel BuildRequires: qoauth-devel BuildRequires: ImageMagick BuildRequires: file-devel Requires: qt Requires: qjson Requires: qoauth Requires: qca-ossl %description Dianara is a Pump.io client, a desktop application for GNU/Linux that allows users to manage their Pump.io social networking accounts without the need to use a web browser. %prep %setup -qn %{name}-v%{version} %build qmake-qt4 make %install %define apps %{_datadir}/applications/ %define pixmaps %{_datadir}/pixmaps/ %define locale %{_datadir}/%{name}/locale/ rm -rf %{buildroot} mkdir -p %{buildroot}%{_bindir}/ cp -p %{name} %{buildroot}%{_bindir}/ mkdir -p %{buildroot}%{apps} cp -p %{name}.desktop %{buildroot}%{apps} mkdir -p %{buildroot}%{pixmaps} cp -p icon/64x64/%{name}.png %{buildroot}%{pixmaps}%{name}.png mkdir -p %{buildroot}%{locale} cp -p translations/*.qm %{buildroot}%{locale} %files %doc CHANGELOG LICENSE README BUGS TODO %{_bindir}/%{name} %{apps}%{name}.desktop %{pixmaps}%{name}.png %{locale}*.qm %changelog * Tue Aug 18 2015 Roman Yepishev 1.3.1-1 - Upgrade to 1.3.1 * Wed Oct 30 2013 Silvio Amici 1.0-3 - Upgrade from beta2 to release, fixed i686 package problem * Thu Oct 24 2013 Silvio Amici 1.0-2 - Dianara 1.0-2 package creation dianara-v1.3.2/packaging/salix-slkbuild/000755 000764 000764 00000000000 12607763123 017556 5ustar00janjan000000 000000 dianara-v1.3.2/packaging/salix-slkbuild/SLKBUILD000644 000764 000764 00000002505 12607735667 020730 0ustar00janjan000000 000000 # Maintainer: Your Name pkgname=dianara pkgver=1.3.1 pkgrel=1salix url="http://dianara.nongnu.org/" source=(http://download-mirror.savannah.gnu.org/releases/$pkgname/$pkgname-v$pkgver.tar.gz) docs=("BUGS" "CHANGELOG" "INSTALL" "LICENSE" "README" "TODO" "TRANSLATING") slackdesc=\ ( #|-----handy-ruler------------------------------------------------------| "dianara (A Pump.io client)" "Dianara is a Pump.io application for the desktop." "With it, you can access your Pump.io account without using a web" "browser." ) build() { cd $startdir/src/$pkgname-v$pkgver qmake PREFIX=/usr make || return 1 # bin install -Dm755 $pkgname \ "$startdir/pkg/usr/bin/$pkgname" # desktop file install -Dm644 $pkgname.desktop \ "$startdir/pkg/usr/share/applications/$pkgname.desktop" # icons install -Dm644 icon/32x32/$pkgname.png \ "$startdir/pkg/usr/share/icons/hicolor/32x32/apps/$pkgname.png" install -Dm644 icon/64x64/$pkgname.png \ "$startdir/pkg/usr/share/icons/hicolor/64x64/apps/$pkgname.png" # translations install -d "$startdir/pkg/usr/share/$pkgname/translations" install -Dm644 translations/*.qm \ "$startdir/pkg/usr/share/$pkgname/translations" # man install -Dm644 manual/$pkgname.1 \ "$startdir/pkg/usr/share/man/man1/$pkgname.1" } } dianara-v1.3.2/src/000755 000764 000764 00000000000 12614520205 013460 5ustar00janjan000000 000000 dianara-v1.3.2/src/main.cpp000644 000764 000764 00000023440 12614433076 015124 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include #include #include #include "mainwindow.h" #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) void customMessageHandlerQt4(QtMsgType type, const char *msg) { // do nothing Q_UNUSED(type) Q_UNUSED(msg) // FIXME, memory leak? return; } #else void customMessageHandlerQt5(QtMsgType type, const QMessageLogContext &context, const QString &msg) { Q_UNUSED(type) Q_UNUSED(context) Q_UNUSED(msg) // Do nothing return; } #endif int main(int argc, char *argv[]) { QApplication dianaraApp(argc, argv); dianaraApp.setApplicationName("Dianara"); dianaraApp.setApplicationVersion("1.3.2"); dianaraApp.setOrganizationName("JanCoding"); dianaraApp.setOrganizationDomain("jancoding.wordpress.com"); std::cout << QString("Dianara v%1 - JanKusanagi 2012-2015\n" "https://jancoding.wordpress.com/dianara\n\n") .arg(dianaraApp.applicationVersion()).toStdString(); std::cout << QString("- Built with Qt v%1").arg(QT_VERSION_STR) .toStdString(); /* * REPRODUCIBLEBUILD is defined via .pro file when SOURCE_DATE_EPOCH is * defined in the build environment. This is used to avoid hardcoding * timestamps and this way make the builds reproducible. * */ #ifndef REPRODUCIBLEBUILD std::cout << QString(" on %1, %2") .arg(__DATE__) .arg(__TIME__).toStdString(); #endif std::cout << "\n"; std::cout << QString("- Running with Qt v%1\n\n").arg(qVersion()) .toStdString(); std::cout.flush(); // To make the mswin version of Dianara distributable as a standalone package #ifdef Q_OS_WIN dianaraApp.addLibraryPath("./plugins/"); #endif QStringList cmdLine = qApp->arguments(); cmdLine.removeFirst(); // Remove the program executable name/path bool debugMode = false; bool nextParameterIsConfig = false; bool ignoreSslErrors = false; bool nohttps = false; // Parse command line parameters if (!cmdLine.isEmpty()) { foreach (QString argument, cmdLine) { // Help if (argument.startsWith("--help", Qt::CaseInsensitive) || argument.startsWith("-h", Qt::CaseInsensitive)) { std::cout << "\nHelp:\n"; std::cout << " " << argv[0] << " [options]\n\n"; std::cout << "Options:\n"; std::cout << " -c --config [name] Use a different " "configuration file\n"; std::cout << " -d --debug Show debug messages " "in the terminal\n"; std::cout << " --linkcolor=[color] Set color for " "links, such as 'green' or '#00B500'\n" "\n"; std::cout << " --nohttps Use this if your " "server does not support HTTPS\n"; std::cout << " --ignoresslerrors Ignore SSL errors " "(dangerous, use with care!)\n" "\n"; std::cout << " -v --version Show version " "information and exit\n"; std::cout << " -h --help Show this help and " "exit\n"; std::cout << "\n"; return 0; // Exit to shell } // Version info if (argument.startsWith("--version", Qt::CaseInsensitive) || argument.startsWith("-v", Qt::CaseInsensitive)) { // Version information already shown std::cout << "\n" "You can get the latest stable version from " "http://dianara.nongnu.org and\n" "the latest development version from " "https://gitlab.com/dianara/dianara-dev" "\n\n"; return 0; } // Use different config file, by setting a different applicationName if (argument.startsWith("--config", Qt::CaseInsensitive) || argument.startsWith("-c", Qt::CaseInsensitive)) { nextParameterIsConfig = true; cmdLine.removeAll(argument); } else if (nextParameterIsConfig) { QString configName = "Dianara_" + argument.toLower(); dianaraApp.setApplicationName(configName); nextParameterIsConfig = false; std::cout << "Using alternate config file: " << configName.toStdString() << "\n"; } // Debug mode if (argument.startsWith("--debug", Qt::CaseInsensitive) || argument.startsWith("-d", Qt::CaseInsensitive)) { debugMode = true; cmdLine.removeAll(argument); std::cout << "Debug messages enabled\n"; } // Option to set link color (useful in GTK environments) if (argument.startsWith("--linkcolor=", Qt::CaseInsensitive)) { QString linkColorName = argument.split("=").last(); QColor linkColor = QColor(linkColorName); if (linkColor.isValid()) { QPalette newPalette = dianaraApp.palette(); newPalette.setColor(QPalette::Link, linkColor); dianaraApp.setPalette(newPalette); std::cout << "Forcing link color to: " << linkColorName.toStdString() << "\n"; } else { std::cout << "Invalid link color specified!\n"; } } // Option to use non-https servers if (argument.startsWith("--nohttps", Qt::CaseInsensitive)) { nohttps = true; cmdLine.removeAll(argument); std::cout << "*** Using No-HTTPS mode\n"; } // Option to ignore SSL errors if (argument.startsWith("--ignoresslerrors", Qt::CaseInsensitive)) { ignoreSslErrors = true; cmdLine.removeAll(argument); std::cout << "*************************************\n" "*** WARNING: Ignoring SSL errors. ***\n" "*** This is insecure! ***\n" "*************************************\n"; } } // End foreach } // Register custom message handler, to hide debug messages unless specified if (!debugMode) { #ifdef Q_OS_WIN FreeConsole(); #endif std::cout << "To see debug messages while running, use --debug\n"; #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) qInstallMsgHandler(customMessageHandlerQt4); #else qInstallMessageHandler(customMessageHandlerQt5); #endif } // Load translation files // Get language from LANG environment variable or system's locale QString languageString = qgetenv("LANG"); if (languageString.isEmpty()) { languageString = QLocale::system().name(); } QString languageFile; bool languageLoaded; QTranslator translatorQt; languageFile = QString("qt_%1").arg(languageString); std::cout << "\n" << "Using Qt translation " << QLibraryInfo::location(QLibraryInfo::TranslationsPath).toStdString() << "/" << languageFile.toStdString() << "... "; languageLoaded = translatorQt.load(languageFile, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); if (languageLoaded) { std::cout << "OK"; dianaraApp.installTranslator(&translatorQt); } else { std::cout << "Unavailable"; } QTranslator translatorDianara; languageFile = QString(":/translations/dianara_%1").arg(languageString); std::cout << "\n" << "Using program translation " << languageFile.toStdString() << "... "; languageLoaded = translatorDianara.load(languageFile); if (languageLoaded) { std::cout << "OK"; dianaraApp.installTranslator(&translatorDianara); } else { std::cout << "Unavailable"; } std::cout << "\n\n"; std::cout.flush(); MainWindow dianaraWindow; if (ignoreSslErrors) { dianaraWindow.enableIgnoringSslErrors(); } if (nohttps) { dianaraWindow.enableNoHttpsMode(); } dianaraWindow.toggleMainWindow(true); // show(), firstTime=true return dianaraApp.exec(); } dianara-v1.3.2/src/mainwindow.cpp000644 000764 000764 00000346120 12613256542 016357 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { this->setWindowTitle("Dianara"); this->setWindowIcon(QIcon::fromTheme("dianara", QIcon(":/icon/64x64/dianara.png"))); this->setMinimumSize(400, 400); QSettings settings; firstRun = true; prepareDataDirectory(); // This sets this->dataDirectory reallyQuitProgram = false; trayIconAvailable = false; trayCurrentNewCount = 0; trayCurrentHLCount = 0; qDebug() << "Qt widget style in use:" << qApp->style()->objectName(); QString currentIconset = QIcon::themeName(); qDebug() << "System iconset:" << currentIconset; qDebug() << "Icon theme search paths:" << QIcon::themeSearchPaths(); if (currentIconset.isEmpty() || currentIconset == "hicolor") { qDebug() << ">> No system iconset (or hicolor) configured; trying to use Oxygen"; QIcon::setThemeName("oxygen"); // VERY TMP; FIXME } #if 0 // 1 to test the fallback icons QIcon::setThemeSearchPaths(QStringList() << "./"); #endif // Network control pumpController = new PumpController(this); // Global object to connect different classes directly globalObject = new GlobalObject(this); // Filter checker filterChecker = new FilterChecker(this); ////// GUI // User's profile editor, in its own window profileEditor = new ProfileEditor(pumpController, this); // Splitter to divide the window horizontally mainSplitter = new QSplitter(Qt::Horizontal, this); mainSplitter->setChildrenCollapsible(false); mainSplitter->setContentsMargins(0, 0, 0, 0); mainSplitter->setHandleWidth(4); // Left side avatarIconButton = new QPushButton(this); avatarIconButton->setIcon(QIcon(QPixmap(":/images/no-avatar.png") .scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation))); avatarIconButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); avatarIconButton->setIconSize(QSize(64, 64)); avatarIconButton->setStyleSheet("QPushButton { border: 4px; " " padding: 4px } " "QPushButton:hover { border: 4px ridge " " palette(highlight);" " border-radius: 8px };"); connect(avatarIconButton, SIGNAL(clicked()), profileEditor, SLOT(show())); fullNameLabel = new QLabel("[-------------]", this); fullNameLabel->setWordWrap(true); fullNameLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum); QFont userDetailsFont; userDetailsFont.setBold(true); userDetailsFont.setItalic(true); userDetailsFont.setPointSize(userDetailsFont.pointSize() - 2); userIdLabel = new QLabel(this); userIdLabel->setWordWrap(true); userIdLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum); userIdLabel->setFont(userDetailsFont); userDetailsFont.setBold(false); userDetailsFont.setItalic(false); userHometownLabel = new QLabel(this); userHometownLabel->setWordWrap(true); userHometownLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum); userHometownLabel->setFont(userDetailsFont); userInfoLayout = new QVBoxLayout(); userInfoLayout->addSpacing(2); userInfoLayout->addWidget(fullNameLabel); userInfoLayout->addWidget(userIdLabel); userInfoLayout->addWidget(userHometownLabel); leftTopLayout = new QHBoxLayout(); leftTopLayout->setContentsMargins(0, 0, 0, 0); leftTopLayout->addWidget(avatarIconButton, 0, Qt::AlignLeft); leftTopLayout->addSpacing(2); leftTopLayout->addLayout(userInfoLayout, 1); leftPanel = new QToolBox(this); // Will hold the minor feeds leftPanel->setContentsMargins(0, 0, 0, 0); //////// Meanwhile feed: inbox/minor meanwhileFeed = new MinorFeed(PumpController::MinorFeedMainRequest, pumpController, globalObject, filterChecker, this); connect(meanwhileFeed, SIGNAL(newItemsCountChanged(int,int)), this, SLOT(setMinorFeedTitle(int,int))); connect(meanwhileFeed, SIGNAL(newItemsReceived(PumpController::requestTypes,int,int,int,int)), this, SLOT(notifyMinorFeedUpdate(PumpController::requestTypes,int,int,int,int))); leftPanel->addItem(meanwhileFeed, QIcon::fromTheme("clock", QIcon(":/images/feed-clock.png")), "*meanwhile*"); this->setMinorFeedTitle(0, 0); // Set initial title (0 new, 0 HL) leftPanel->setItemToolTip(0, "" + tr("Minor activities done by everyone, such " "as replying to posts")); //////// Mentions feed: inbox/direct/minor mentionsFeed = new MinorFeed(PumpController::MinorFeedDirectRequest, pumpController, globalObject, filterChecker, this); connect(mentionsFeed, SIGNAL(newItemsCountChanged(int,int)), this, SLOT(setMentionsFeedTitle(int,int))); connect(mentionsFeed, SIGNAL(newItemsReceived(PumpController::requestTypes,int,int,int,int)), this, SLOT(notifyMinorFeedUpdate(PumpController::requestTypes,int,int,int,int))); leftPanel->addItem(mentionsFeed, QIcon::fromTheme("mail-folder-inbox", QIcon(":/images/feed-inbox.png")), "*mentions*"); this->setMentionsFeedTitle(0, 0); // Set initial title, with 0 new leftPanel->setItemToolTip(1, "" + tr("Minor activities addressed to you")); //////// Actions feed: feed/minor actionsFeed = new MinorFeed(PumpController::MinorFeedActivityRequest, pumpController, globalObject, filterChecker, this); connect(actionsFeed, SIGNAL(newItemsReceived(PumpController::requestTypes,int,int,int,int)), this, SLOT(notifyMinorFeedUpdate(PumpController::requestTypes,int,int,int,int))); leftPanel->addItem(actionsFeed, QIcon::fromTheme("mail-folder-outbox", QIcon(":/images/feed-outbox.png")), PumpController::getFeedNameAndPath(PumpController::MinorFeedActivityRequest) .first()); leftPanel->setItemToolTip(2, "" + tr("Minor activities done by you")); leftSideWidget = new QWidget(this); leftLayout = new QVBoxLayout(); leftLayout->setSpacing(1); // Minimal spacing leftLayout->setContentsMargins(0, 0, 0, 0); leftLayout->addLayout(leftTopLayout); // Avatar + user info leftLayout->addWidget(leftPanel); // Meanwhile and other minor feeds // Shortcuts to change between the different minor feeds this->showMeanwhileFeed = new QAction(this); showMeanwhileFeed->setShortcut(QKeySequence("Ctrl+1")); connect(showMeanwhileFeed, SIGNAL(triggered()), this, SLOT(toggleMeanwhileFeed())); this->addAction(showMeanwhileFeed); this->showMentionsFeed = new QAction(this); showMentionsFeed->setShortcut(QKeySequence("Ctrl+2")); connect(showMentionsFeed, SIGNAL(triggered()), this, SLOT(toggleMentionsFeed())); this->addAction(showMentionsFeed); this->showActionsFeed = new QAction(this); showActionsFeed->setShortcut(QKeySequence("Ctrl+3")); connect(showActionsFeed, SIGNAL(triggered()), this, SLOT(toggleActionsFeed())); this->addAction(showActionsFeed); //////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////// TMP GROUP MANAGER STUFF //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// #ifdef GROUPSUPPORT GroupsManager *TMPGROUPSMANAGER = new GroupsManager(this->pumpController, this); QPushButton *TMPEDITGROUPSBUTTON = new QPushButton(QIcon::fromTheme("user-group-properties"), "*MANAGE GROUPS*"); connect(TMPEDITGROUPSBUTTON, SIGNAL(clicked()), TMPGROUPSMANAGER, SLOT(show())); this->leftLayout->addWidget(TMPEDITGROUPSBUTTON); #endif //////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////// TMP GROUP MANAGER STUFF //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// this->leftSideWidget->setLayout(leftLayout); // Right side rightSideWidget = new QWidget(); rightLayout = new QVBoxLayout(); rightLayout->setContentsMargins(0, 1, 1, 1); publisher = new Publisher(pumpController, globalObject, this); /// START SETTING UP TIMELINES // Main timeline // mainTimeline = new TimeLine(PumpController::MainTimelineRequest, pumpController, globalObject, filterChecker, this); mainTimelineScrollArea = new QScrollArea(this); mainTimelineScrollArea->setContentsMargins(1, 1, 1, 1); mainTimelineScrollArea->setFrameStyle(QFrame::NoFrame); mainTimelineScrollArea->setWidget(mainTimeline); // Make timeline scrollable mainTimelineScrollArea->setWidgetResizable(true); mainTimelineScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(mainTimeline, SIGNAL(scrollTo(QAbstractSlider::SliderAction)), this, SLOT(scrollMainTimelineTo(QAbstractSlider::SliderAction))); connect(mainTimeline, SIGNAL(unreadPostsCountChanged(PumpController::requestTypes,int,int,int)), this, SLOT(setTimelineTabTitle(PumpController::requestTypes,int,int,int))); connect(mainTimeline, SIGNAL(timelineRendered(PumpController::requestTypes,int,int,int,int,int)), this, SLOT(notifyTimelineUpdate(PumpController::requestTypes,int,int,int,int,int))); // To ensure comment composer is visible connect(mainTimeline, SIGNAL(commentingOnPost(QWidget*)), this, SLOT(scrollMainTimelineToWidget(QWidget*))); // Direct timeline // directTimeline = new TimeLine(PumpController::DirectTimelineRequest, pumpController, globalObject, filterChecker, this); directTimelineScrollArea = new QScrollArea(this); directTimelineScrollArea->setContentsMargins(1, 1, 1, 1); directTimelineScrollArea->setFrameStyle(QFrame::NoFrame); directTimelineScrollArea->setWidget(directTimeline); directTimelineScrollArea->setWidgetResizable(true); directTimelineScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(directTimeline, SIGNAL(scrollTo(QAbstractSlider::SliderAction)), this, SLOT(scrollDirectTimelineTo(QAbstractSlider::SliderAction))); connect(directTimeline, SIGNAL(unreadPostsCountChanged(PumpController::requestTypes,int,int,int)), this, SLOT(setTimelineTabTitle(PumpController::requestTypes,int,int,int))); connect(directTimeline, SIGNAL(timelineRendered(PumpController::requestTypes,int,int,int,int,int)), this, SLOT(notifyTimelineUpdate(PumpController::requestTypes,int,int,int,int,int))); // To ensure comment composer is visible connect(directTimeline, SIGNAL(commentingOnPost(QWidget*)), this, SLOT(scrollDirectTimelineToWidget(QWidget*))); // Activity timeline // activityTimeline = new TimeLine(PumpController::ActivityTimelineRequest, pumpController, globalObject, filterChecker, this); activityTimelineScrollArea = new QScrollArea(this); activityTimelineScrollArea->setContentsMargins(1, 1, 1, 1); activityTimelineScrollArea->setFrameStyle(QFrame::NoFrame); activityTimelineScrollArea->setWidget(activityTimeline); // Make it scrollable activityTimelineScrollArea->setWidgetResizable(true); activityTimelineScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(activityTimeline, SIGNAL(scrollTo(QAbstractSlider::SliderAction)), this, SLOT(scrollActivityTimelineTo(QAbstractSlider::SliderAction))); connect(activityTimeline, SIGNAL(unreadPostsCountChanged(PumpController::requestTypes,int,int,int)), this, SLOT(setTimelineTabTitle(PumpController::requestTypes,int,int,int))); connect(activityTimeline, SIGNAL(timelineRendered(PumpController::requestTypes,int,int,int,int,int)), this, SLOT(notifyTimelineUpdate(PumpController::requestTypes,int,int,int,int,int))); // To ensure comment composer is visible connect(activityTimeline, SIGNAL(commentingOnPost(QWidget*)), this, SLOT(scrollActivityTimelineToWidget(QWidget*))); // Favorites timeline // favoritesTimeline = new TimeLine(PumpController::FavoritesTimelineRequest, pumpController, globalObject, filterChecker, this); favoritesTimelineScrollArea = new QScrollArea(this); favoritesTimelineScrollArea->setContentsMargins(1, 1, 1, 1); favoritesTimelineScrollArea->setFrameStyle(QFrame::NoFrame); favoritesTimelineScrollArea->setWidget(favoritesTimeline); favoritesTimelineScrollArea->setWidgetResizable(true); favoritesTimelineScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(favoritesTimeline, SIGNAL(scrollTo(QAbstractSlider::SliderAction)), this, SLOT(scrollFavoritesTimelineTo(QAbstractSlider::SliderAction))); connect(favoritesTimeline, SIGNAL(unreadPostsCountChanged(PumpController::requestTypes,int,int,int)), this, SLOT(setTimelineTabTitle(PumpController::requestTypes,int,int,int))); connect(favoritesTimeline, SIGNAL(timelineRendered(PumpController::requestTypes,int,int,int,int,int)), this, SLOT(notifyTimelineUpdate(PumpController::requestTypes,int,int,int,int,int))); // To ensure comment composer is visible connect(favoritesTimeline, SIGNAL(commentingOnPost(QWidget*)), this, SLOT(scrollFavoritesTimelineToWidget(QWidget*))); /// END SETTING UP TIMELINES // The contact list has its own tabs with its own scroll areas contactManager = new ContactManager(pumpController, globalObject, this); tabWidget = new QTabWidget(this); tabWidget->addTab(mainTimelineScrollArea, QIcon::fromTheme("view-list-details", QIcon(":/images/feed-inbox.png")), "*MAIN TIMELINE TAB*"); this->setTimelineTabTitle(PumpController::MainTimelineRequest, 0, 0, 0); tabWidget->addTab(directTimelineScrollArea, QIcon::fromTheme("mail-message", QIcon(":/images/feed-inbox.png")), "*MESSAGES TAB*"); this->setTimelineTabTitle(PumpController::DirectTimelineRequest, 0, 0, 0); tabWidget->addTab(activityTimelineScrollArea, QIcon::fromTheme("user-home", QIcon(":/images/feed-outbox.png")), "*ACTIVITY TAB*"); this->setTimelineTabTitle(PumpController::ActivityTimelineRequest, 0, 0, 0); tabWidget->addTab(favoritesTimelineScrollArea, QIcon::fromTheme("folder-favorites", QIcon(":/images/button-like.png")), "*FAVORITES TAB*"); this->setTimelineTabTitle(PumpController::FavoritesTimelineRequest, 0, 0, 0); tabWidget->addTab(contactManager, QIcon::fromTheme("system-users", QIcon(":/images/no-avatar.png")), tr("&Contacts")); tabWidget->setTabToolTip(4, "" // HTMLized for wordwrap + tr("The people you follow, the ones who " "follow you, and your person lists")); connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(setTitleAndTrayInfo(int))); rightLayout->addWidget(publisher, 1); // stretch 1/10 rightLayout->addWidget(tabWidget, 9); // stretch 9/10 this->rightSideWidget->setLayout(rightLayout); mainSplitter->addWidget(leftSideWidget); mainSplitter->addWidget(rightSideWidget); this->setCentralWidget(mainSplitter); // FreeDesktop.org notifications handler fdNotifier = new FDNotifications(this); connect(fdNotifier, SIGNAL(showFallbackNotification(QString,QString)), this, SLOT(showTrayFallbackMessage(QString,QString))); // D-Bus Interface, for remote control #ifdef QT_DBUS_LIB this->dbusInterface = new DBusInterface(this); QDBusConnection bus = QDBusConnection::sessionBus(); bus.registerObject("/Dianara", dbusInterface, QDBusConnection::ExportAllSlots); bus.registerService("org.nongnu." + qApp->applicationName().toLower()); #endif // Timeline updates timer updateTimer = new QTimer(this); // Interval is set from loadSettings() connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateMainAndMinorTimelines())); updateTimer->start(); // Timestamps refresh timer timestampsTimer = new QTimer(this); timestampsTimer->setInterval(60000); // 60 sec connect(timestampsTimer, SIGNAL(timeout()), this, SLOT(refreshAllTimestamps())); timestampsTimer->start(); // Delayed timeline resize timer delayedResizeTimer = new QTimer(this); delayedResizeTimer->setSingleShot(true); connect(delayedResizeTimer, SIGNAL(timeout()), this, SLOT(adjustTimelineSizes())); favoritesReloadTimer = new QTimer(this); favoritesReloadTimer->setSingleShot(true); favoritesReloadTimer->setInterval(300000); // 5 minutes connect(favoritesReloadTimer, SIGNAL(timeout()), this, SLOT(updateFavoritesTimeline())); /* * User-did-something timer * * Every time the user does something minor, such as commenting or * following a contact, the timer will be restarted. * * Some time after the user has stopped doing things, the timer * will update the Actions feed. * */ userDidSomethingTimer = new QTimer(this); userDidSomethingTimer->setSingleShot(true); userDidSomethingTimer->setInterval(300000); // 5 minutes connect(userDidSomethingTimer, SIGNAL(timeout()), this, SLOT(updateActionsFeed())); connect(pumpController, SIGNAL(userDidSomething()), userDidSomethingTimer, SLOT(start())); // Restart timer every time delayedScrollTimer = new QTimer(this); delayedScrollTimer->setSingleShot(true); connect(delayedScrollTimer, SIGNAL(timeout()), this, SLOT(scrollToNewPosts())); ////////////////// Load configuration from disk loadSettings(); //// External widgets which live in their own windows // Account wizard accountDialog = new AccountDialog(pumpController, this); connect(accountDialog, SIGNAL(userIDChanged(QString)), this, SLOT(updateUserID(QString))); // Configuration dialog configDialog = new ConfigDialog(this->globalObject, this->dataDirectory, this->updateInterval, this->tabsPosition, this->tabsMovable, this->fdNotifier, this); connect(configDialog, SIGNAL(configurationChanged()), this, SLOT(updateConfigSettings())); // Filter editor filterEditor = new FilterEditor(filterChecker, this); connect(configDialog, SIGNAL(filterEditorRequested()), filterEditor, SLOT(show())); // Log viewer logViewer = new LogViewer(this); connect(pumpController, SIGNAL(logMessage(QString,QString)), logViewer, SLOT(addToLog(QString,QString))); connect(globalObject, SIGNAL(messageForLog(QString,QString)), logViewer, SLOT(addToLog(QString,QString))); // Help widget; "Getting started", etc. helpWidget = new HelpWidget(this); /// ///////////////// Connections between Meanwhile feed and the timelines /// /// /// Object updated connect(meanwhileFeed, SIGNAL(objectUpdated(ASObject*)), mainTimeline, SLOT(updatePostsFromMinorFeed(ASObject*))); connect(meanwhileFeed, SIGNAL(objectUpdated(ASObject*)), directTimeline, SLOT(updatePostsFromMinorFeed(ASObject*))); connect(meanwhileFeed, SIGNAL(objectUpdated(ASObject*)), activityTimeline, SLOT(updatePostsFromMinorFeed(ASObject*))); // Object liked connect(meanwhileFeed, SIGNAL(objectLiked(QString,QString,QString,QString,QString)), mainTimeline, SLOT(addLikesFromMinorFeed(QString,QString,QString,QString,QString))); connect(meanwhileFeed, SIGNAL(objectLiked(QString,QString,QString,QString,QString)), directTimeline, SLOT(addLikesFromMinorFeed(QString,QString,QString,QString,QString))); connect(meanwhileFeed, SIGNAL(objectLiked(QString,QString,QString,QString,QString)), activityTimeline, SLOT(addLikesFromMinorFeed(QString,QString,QString,QString,QString))); // Object unliked connect(meanwhileFeed, SIGNAL(objectUnliked(QString,QString,QString)), mainTimeline, SLOT(removeLikesFromMinorFeed(QString,QString,QString))); connect(meanwhileFeed, SIGNAL(objectUnliked(QString,QString,QString)), directTimeline, SLOT(removeLikesFromMinorFeed(QString,QString,QString))); connect(meanwhileFeed, SIGNAL(objectUnliked(QString,QString,QString)), activityTimeline, SLOT(removeLikesFromMinorFeed(QString,QString,QString))); // Object got a new reply connect(meanwhileFeed, SIGNAL(objectReplyAdded(ASObject*)), mainTimeline, SLOT(addReplyFromMinorFeed(ASObject*))); connect(meanwhileFeed, SIGNAL(objectReplyAdded(ASObject*)), directTimeline, SLOT(addReplyFromMinorFeed(ASObject*))); connect(meanwhileFeed, SIGNAL(objectReplyAdded(ASObject*)), activityTimeline, SLOT(addReplyFromMinorFeed(ASObject*))); /// Object deleted connect(meanwhileFeed, SIGNAL(objectDeleted(ASObject*)), mainTimeline, SLOT(setPostsDeletedFromMinorFeed(ASObject*))); connect(meanwhileFeed, SIGNAL(objectDeleted(ASObject*)), directTimeline, SLOT(setPostsDeletedFromMinorFeed(ASObject*))); connect(meanwhileFeed, SIGNAL(objectDeleted(ASObject*)), activityTimeline, SLOT(setPostsDeletedFromMinorFeed(ASObject*))); // TODO: sync other timelines/feeds, too -- FIXME /// //////////////////////////////// Connections for PumpController ////////// /// connect(pumpController, SIGNAL(profileReceived(QString,QString,QString,QString,QString)), this, SLOT(updateProfileData(QString,QString,QString,QString,QString))); connect(pumpController, SIGNAL(avatarPictureReceived(QByteArray,QString)), this, SLOT(storeAvatar(QByteArray,QString))); connect(pumpController, SIGNAL(imageReceived(QByteArray,QString)), this, SLOT(storeImage(QByteArray,QString))); connect(pumpController, SIGNAL(authorizationStatusChanged(bool)), this, SLOT(toggleWidgetsByAuthorization(bool))); connect(pumpController, SIGNAL(authorizationFailed(QString,QString)), this, SLOT(showAuthError(QString,QString))); connect(pumpController, SIGNAL(initializationCompleted()), this, SLOT(onInitializationComplete())); // After receiving timeline contents, update corresponding timeline connect(pumpController, SIGNAL(mainTimelineReceived(QVariantList,QString,QString,int)), mainTimeline, SLOT(setTimeLineContents(QVariantList,QString,QString,int))); connect(pumpController, SIGNAL(directTimelineReceived(QVariantList,QString,QString,int)), directTimeline, SLOT(setTimeLineContents(QVariantList,QString,QString,int))); connect(pumpController, SIGNAL(activityTimelineReceived(QVariantList,QString,QString,int)), activityTimeline, SLOT(setTimeLineContents(QVariantList,QString,QString,int))); connect(pumpController, SIGNAL(favoritesTimelineReceived(QVariantList,QString,QString,int)), favoritesTimeline, SLOT(setTimeLineContents(QVariantList,QString,QString,int))); // After receiving a main minor feed (Meanwhile, Mentions, Actions), update them connect(pumpController, SIGNAL(minorFeedMainReceived(QVariantList,QString,QString,int)), meanwhileFeed, SLOT(setFeedContents(QVariantList,QString,QString,int))); connect(pumpController, SIGNAL(minorFeedDirectReceived(QVariantList,QString,QString,int)), mentionsFeed, SLOT(setFeedContents(QVariantList,QString,QString,int))); connect(pumpController, SIGNAL(minorFeedActivityReceived(QVariantList,QString,QString,int)), actionsFeed, SLOT(setFeedContents(QVariantList,QString,QString,int))); // After sucessful posting, request updated timeline, with your post included connect(pumpController, SIGNAL(postPublished()), this, SLOT(updateMainActivityMinorTimelines())); // After successful liking, update likes count connect(pumpController, SIGNAL(likesReceived(QVariantList,QString)), mainTimeline, SLOT(setLikesInPost(QVariantList,QString))); connect(pumpController, SIGNAL(likesReceived(QVariantList,QString)), directTimeline, SLOT(setLikesInPost(QVariantList,QString))); connect(pumpController, SIGNAL(likesReceived(QVariantList,QString)), activityTimeline, SLOT(setLikesInPost(QVariantList,QString))); // We don't update likes count in favorites timeline, // since it still doesn't know about this post // Instead, schedule to reload favorites timeline connect(pumpController, SIGNAL(likeSet()), favoritesReloadTimer, SLOT(start())); // After commenting successfully, refresh list of comments in that post connect(pumpController, SIGNAL(commentsReceived(QVariantList,QString)), mainTimeline, SLOT(setCommentsInPost(QVariantList,QString))); connect(pumpController, SIGNAL(commentsReceived(QVariantList,QString)), directTimeline, SLOT(setCommentsInPost(QVariantList,QString))); connect(pumpController, SIGNAL(commentsReceived(QVariantList,QString)), activityTimeline, SLOT(setCommentsInPost(QVariantList,QString))); connect(pumpController, SIGNAL(commentsReceived(QVariantList,QString)), favoritesTimeline, SLOT(setCommentsInPost(QVariantList,QString))); // After successful sharing.... // TODO*** // Show notifications for events sent from pumpController connect(pumpController, SIGNAL(showNotification(QString)), fdNotifier, SLOT(showMessage(QString))); // Update statusBar message from pumpController's infos connect(pumpController, SIGNAL(currentJobChanged(QString)), this, SLOT(setStatusBarMessage(QString))); connect(pumpController, SIGNAL(transientStatusBarMessage(QString)), this, SLOT(setTransientStatusMessage(QString))); // Update statusBar also from globalObject's requests connect(globalObject, SIGNAL(messageForStatusBar(QString)), this, SLOT(setStatusBarMessage(QString))); // Show a user's timeline when requested through GlobalObject connect(globalObject, SIGNAL(userTimelineRequested(QString,QString,QIcon,QString)), this, SLOT(showUserTimeline(QString,QString,QIcon,QString))); // Add menus and toolbar createMenus(); createToolbar(); // Info label in the right side of the menu bar this->menuInfoLabel = new QLabel(tr("Press F1 for help"), // Just initial text this); menuInfoLabel->setFont(userDetailsFont); menuInfoLabel->setAlignment(Qt::AlignRight); this->menuInfoLayout = new QHBoxLayout(); menuInfoLayout->addWidget(menuInfoLabel); this->menuInfoWidget = new QWidget(this); menuInfoWidget->setLayout(menuInfoLayout); this->menuBar()->setCornerWidget(menuInfoWidget); // If menu is being displayed outside the application, hide this, too this->menuInfoWidget->setVisible(!this->menuBar()->isNativeMenuBar()); // StatusBar stuff this->createStatusbarWidgets(); this->statusBar()->showMessage(tr("Initializing...")); // Add the system tray icon createTrayIcon(); settings.beginGroup("MainWindow"); // Now set the "view side panel" checkable menu to its saved state // This will also trigger the action to hide/show it viewSidePanel->setChecked(settings.value("viewSidePanel", true).toBool()); // Check view > toolbar if needed; HIDDEN by default viewToolbar->setChecked(settings.value("viewToolbar", false).toBool()); // Set the "view status bar" checkable menu to its saved state viewStatusBar->setChecked(settings.value("viewStatusBar", true).toBool()); settings.endGroup(); initializationComplete = false; logViewer->addToLog(tr("Dianara started.")); logViewer->addToLog(tr("Running with Qt v%1.").arg(qVersion())); this->statusAccountButtonUsed = false; // If User ID is defined, set PumpController in motion if (!userID.isEmpty() && pumpController->currentlyAuthorized()) { pumpController->setUserCredentials(this->userID); fdNotifier->setCurrentUserId(this->userID); // getUserProfile() will be called from setUserCredentials() this->initializationProgressBar->show(); } else // Otherwise, just say so in the statusbar and offer a button there { QString message = tr("Your account is not configured yet."); this->setStatusBarMessage(message); logViewer->addToLog(message); this->statusAccountButton = new QPushButton(QIcon::fromTheme("dialog-password", QIcon(":/images/button-password.png")), tr("Click here to configure " "your account"), this); connect(statusAccountButton, SIGNAL(clicked()), settingsAccount, SLOT(trigger())); this->statusBar()->insertPermanentWidget(0, statusAccountButton); this->statusAccountButtonUsed = true; } // Post-init timer postInitTimer = new QTimer(this); postInitTimer->setSingleShot(true); connect(postInitTimer, SIGNAL(timeout()), this, SLOT(postInit())); postInitTimer->start(1000); // Handle session manager asking to quit connect(qApp, SIGNAL(commitDataRequest(QSessionManager&)), this, SLOT(onSessionManagerQuitRequest(QSessionManager&))); qDebug() << "MainWindow created"; } MainWindow::~MainWindow() { qDebug() << "MainWindow destroyed"; } /* * Prepare the data directory. Create if necessary * */ void MainWindow::prepareDataDirectory() { #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) dataDirectory = QDesktopServices::storageLocation(QDesktopServices::DataLocation); #else dataDirectory = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first(); #endif qDebug() << "Data directory:" << this->dataDirectory; QDir dataDir; // Base directory if (!dataDir.exists(dataDirectory)) { qDebug() << "Creating data directory"; if (dataDir.mkpath(dataDirectory)) { qDebug() << "Data directory created"; } else { qDebug() << "Error creating data directory!"; } } if (!dataDir.exists(dataDirectory + "/images")) { qDebug() << "Creating images directory"; if (dataDir.mkpath(dataDirectory + "/images")) { qDebug() << "Images directory created"; } else { qDebug() << "Error creating images directory!"; } } if (!dataDir.exists(dataDirectory + "/audios")) { qDebug() << "Creating audios directory"; if (dataDir.mkpath(dataDirectory + "/audios")) { qDebug() << "Audios directory created"; } else { qDebug() << "Error creating audios directory!"; } } if (!dataDir.exists(dataDirectory + "/videos")) { qDebug() << "Creating videos directory"; if (dataDir.mkpath(dataDirectory + "/videos")) { qDebug() << "Videos directory created"; } else { qDebug() << "Error creating videos directory!"; } } // Avatars directory if (!dataDir.exists(dataDirectory + "/avatars")) { qDebug() << "Creating avatars directory"; if (dataDir.mkpath(dataDirectory + "/avatars")) { qDebug() << "Avatars directory created"; } else { qDebug() << "Error creating avatars directory!"; } } } /* * Populate the menus * */ void MainWindow::createMenus() { sessionMenu = new QMenu(tr("&Session"), this); QString feedName; QString optionString = tr("Update %1"); // Main timeline feedName = PumpController::getFeedNameAndPath(PumpController::MainTimelineRequest).first(); sessionUpdateMainTimeline = new QAction(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), optionString.arg(feedName), this); sessionUpdateMainTimeline->setShortcut(QKeySequence(Qt::Key_F5)); sessionUpdateMainTimeline->setDisabled(true); // Disabled until authorization checked connect(sessionUpdateMainTimeline, SIGNAL(triggered()), mainTimeline, SLOT(goToFirstPage())); sessionMenu->addAction(sessionUpdateMainTimeline); // Direct messages feedName = PumpController::getFeedNameAndPath(PumpController::DirectTimelineRequest).first(); sessionUpdateDirectTimeline = new QAction(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), optionString.arg(feedName), this); sessionUpdateDirectTimeline->setShortcut(QKeySequence(Qt::Key_F6)); sessionUpdateDirectTimeline->setDisabled(true); connect(sessionUpdateDirectTimeline, SIGNAL(triggered()), directTimeline, SLOT(goToFirstPage())); sessionMenu->addAction(sessionUpdateDirectTimeline); // Activity feedName = PumpController::getFeedNameAndPath(PumpController::ActivityTimelineRequest).first(); sessionUpdateActivityTimeline = new QAction(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), optionString.arg(feedName), this); sessionUpdateActivityTimeline->setShortcut(QKeySequence(Qt::Key_F7)); sessionUpdateActivityTimeline->setDisabled(true); connect(sessionUpdateActivityTimeline, SIGNAL(triggered()), activityTimeline, SLOT(goToFirstPage())); sessionMenu->addAction(sessionUpdateActivityTimeline); // Favorites feedName = PumpController::getFeedNameAndPath(PumpController::FavoritesTimelineRequest).first(); sessionUpdateFavoritesTimeline = new QAction(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), optionString.arg(feedName), this); sessionUpdateFavoritesTimeline->setShortcut(QKeySequence(Qt::Key_F8)); sessionUpdateFavoritesTimeline->setDisabled(true); connect(sessionUpdateFavoritesTimeline, SIGNAL(triggered()), favoritesTimeline, SLOT(goToFirstPage())); sessionMenu->addAction(sessionUpdateFavoritesTimeline); sessionMenu->addSeparator(); // ---------- minor feeds // Meanwhile feedName = PumpController::getFeedNameAndPath(PumpController::MinorFeedMainRequest).first(); sessionUpdateMinorFeedMain = new QAction(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), optionString.arg(feedName), this); sessionUpdateMinorFeedMain->setShortcut(QKeySequence(Qt::Key_F2)); sessionUpdateMinorFeedMain->setDisabled(true); connect(sessionUpdateMinorFeedMain, SIGNAL(triggered()), meanwhileFeed, SLOT(updateFeed())); sessionMenu->addAction(sessionUpdateMinorFeedMain); // Mentions feedName = PumpController::getFeedNameAndPath(PumpController::MinorFeedDirectRequest).first(); sessionUpdateMinorFeedDirect = new QAction(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), optionString.arg(feedName), this); sessionUpdateMinorFeedDirect->setShortcut(QKeySequence(Qt::Key_F3)); sessionUpdateMinorFeedDirect->setDisabled(true); connect(sessionUpdateMinorFeedDirect, SIGNAL(triggered()), mentionsFeed, SLOT(updateFeed())); sessionMenu->addAction(sessionUpdateMinorFeedDirect); // Actions feedName = PumpController::getFeedNameAndPath(PumpController::MinorFeedActivityRequest).first(); sessionUpdateMinorFeedActivity = new QAction(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), optionString.arg(feedName), this); sessionUpdateMinorFeedActivity->setShortcut(QKeySequence(Qt::Key_F4)); sessionUpdateMinorFeedActivity->setDisabled(true); connect(sessionUpdateMinorFeedActivity, SIGNAL(triggered()), this, SLOT(updateActionsFeed())); // Go through this slot to stop the timer sessionMenu->addAction(sessionUpdateMinorFeedActivity); ////////////////////////////////////////////////////////// sessionMenu->addSeparator(); // ------ sessionAutoUpdates = new QAction(QIcon::fromTheme("clock", QIcon(":/images/feed-clock.png")), tr("Auto-update &Timelines"), this); sessionAutoUpdates->setCheckable(true); sessionAutoUpdates->setChecked(true); sessionAutoUpdates->setDisabled(true); // until initialization is complete connect(sessionAutoUpdates, SIGNAL(toggled(bool)), this, SLOT(toggleAutoUpdates(bool))); sessionMenu->addAction(sessionAutoUpdates); sessionMarkAllAsRead = new QAction(QIcon::fromTheme("mail-mark-read"), tr("Mark All as Read"), this); sessionMarkAllAsRead->setShortcut(QKeySequence("Ctrl+R")); connect(sessionMarkAllAsRead, SIGNAL(triggered()), this, SLOT(markAllAsRead())); sessionMenu->addAction(sessionMarkAllAsRead); sessionMenu->addSeparator(); sessionPostNote = new QAction(QIcon::fromTheme("document-edit", QIcon(":/images/button-edit.png")), tr("&Post a Note"), this); sessionPostNote->setShortcut(QKeySequence("Ctrl+N")); connect(sessionPostNote, SIGNAL(triggered()), this, SLOT(startPost())); // Will show window if needed sessionMenu->addAction(sessionPostNote); sessionMenu->addSeparator(); sessionQuit = new QAction(QIcon::fromTheme("application-exit", QIcon(":/images/button-delete.png")), tr("&Quit"), this); sessionQuit->setShortcut(QKeySequence("Ctrl+Q")); connect(sessionQuit, SIGNAL(triggered()), this, SLOT(quitProgram())); sessionMenu->addAction(sessionQuit); this->menuBar()->addMenu(sessionMenu); viewMenu = new QMenu(tr("&View"), this); viewSidePanel = new QAction(QIcon::fromTheme("view-sidetree"), tr("Side &Panel"), this); connect(viewSidePanel, SIGNAL(toggled(bool)), this, SLOT(toggleSidePanel(bool))); viewSidePanel->setCheckable(true); viewSidePanel->setChecked(true); viewSidePanel->setShortcut(Qt::Key_F9); viewMenu->addAction(viewSidePanel); viewToolbar = new QAction(QIcon::fromTheme("configure-toolbars"), tr("&Toolbar"), this); connect(viewToolbar, SIGNAL(toggled(bool)), this, SLOT(toggleToolbar(bool))); viewToolbar->setCheckable(true); viewMenu->addAction(viewToolbar); viewStatusBar = new QAction(QIcon::fromTheme("configure-toolbars"), tr("Status &Bar"), this); connect(viewStatusBar, SIGNAL(toggled(bool)), this, SLOT(toggleStatusBar(bool))); viewStatusBar->setCheckable(true); viewStatusBar->setChecked(true); viewMenu->addAction(viewStatusBar); viewMenu->addSeparator(); viewFullscreenAction = new QAction(QIcon::fromTheme("view-fullscreen"), tr("Full &Screen"), this); connect(viewFullscreenAction, SIGNAL(toggled(bool)), this, SLOT(toggleFullscreen(bool))); viewFullscreenAction->setCheckable(true); viewFullscreenAction->setChecked(false); viewFullscreenAction->setShortcut(Qt::Key_F11); viewMenu->addAction(viewFullscreenAction); viewLogAction = new QAction(QIcon::fromTheme("text-x-log", QIcon(":/images/log.png")), tr("&Log"), this); connect(viewLogAction, SIGNAL(triggered()), logViewer, SLOT(toggleVisibility())); viewLogAction->setShortcut(Qt::Key_F12); viewMenu->addAction(viewLogAction); this->menuBar()->addMenu(viewMenu); settingsMenu = new QMenu(tr("S&ettings"), this); settingsEditProfile = new QAction(QIcon::fromTheme("user-properties", QIcon(":/images/no-avatar.png")), tr("Edit &Profile"), this); settingsEditProfile->setShortcut(QKeySequence("Ctrl+Shift+P")); connect(settingsEditProfile, SIGNAL(triggered()), profileEditor, SLOT(show())); settingsMenu->addAction(settingsEditProfile); settingsAccount = new QAction(QIcon::fromTheme("dialog-password", QIcon(":/images/button-password.png")), tr("&Account"), this); connect(settingsAccount, SIGNAL(triggered()), accountDialog, SLOT(show())); settingsMenu->addAction(settingsAccount); settingsMenu->addSeparator(); settingsFilters = new QAction(QIcon::fromTheme("view-filter", QIcon(":/images/button-filter.png")), tr("&Filters and Highlighting"), this); settingsFilters->setShortcut(QKeySequence("Ctrl+Shift+F")); connect(settingsFilters, SIGNAL(triggered()), filterEditor, SLOT(show())); settingsMenu->addAction(settingsFilters); settingsConfigure = new QAction(QIcon::fromTheme("configure", QIcon(":/images/button-configure.png")), tr("&Configure Dianara"), this); settingsConfigure->setShortcut(QKeySequence("Ctrl+Shift+S")); connect(settingsConfigure, SIGNAL(triggered()), configDialog, SLOT(show())); settingsMenu->addAction(settingsConfigure); this->menuBar()->addMenu(settingsMenu); this->menuBar()->addSeparator(); helpMenu = new QMenu(tr("&Help"), this); helpBasicHelp = new QAction(QIcon::fromTheme("help-browser", QIcon(":/icon/64x64/dianara.png")), tr("Basic &Help"), this); helpBasicHelp->setShortcut(Qt::Key_F1); connect(helpBasicHelp, SIGNAL(triggered()), helpWidget, SLOT(show())); helpMenu->addAction(helpBasicHelp); helpShowWizard = new QAction(QIcon::fromTheme("tools-wizard", QIcon(":/images/button-online.png")), tr("Show Welcome Wizard"), this); connect(helpShowWizard, SIGNAL(triggered()), this, SLOT(showFirstRunWizard())); helpMenu->addAction(helpShowWizard); helpMenu->addSeparator(); // --- helpVisitWebsite = new QAction(QIcon::fromTheme("internet-web-browser", QIcon(":/images/button-download.png")), tr("Visit &Website"), this); connect(helpVisitWebsite, SIGNAL(triggered()), this, SLOT(visitWebSite())); helpMenu->addAction(helpVisitWebsite); helpVisitBugTracker = new QAction(QIcon::fromTheme("tools-report-bug", QIcon(":/images/button-edit.png")), tr("Report a &Bug"), this); // "or Suggest a Feature"? connect(helpVisitBugTracker, SIGNAL(triggered()), this, SLOT(visitBugTracker())); helpMenu->addAction(helpVisitBugTracker); helpMenu->addSeparator(); // --- helpVisitPumpGuide = new QAction(QIcon::fromTheme("help-contents", QIcon(":/images/button-download.png")), tr("Pump.io User &Guide"), this); connect(helpVisitPumpGuide, SIGNAL(triggered()), this, SLOT(visitPumpGuide())); helpMenu->addAction(helpVisitPumpGuide); helpVisitPumpTips = new QAction(QIcon::fromTheme("help-hint", QIcon(":/images/button-download.png")), tr("Some Pump.io &Tips"), this); connect(helpVisitPumpTips, SIGNAL(triggered()), this, SLOT(visitTips())); helpMenu->addAction(helpVisitPumpTips); helpVisitPumpUserList = new QAction(QIcon::fromTheme("system-users", QIcon(":/images/no-avatar.png")), tr("List of Some Pump.io &Users"), this); connect(helpVisitPumpUserList, SIGNAL(triggered()), this, SLOT(visitUserList())); helpMenu->addAction(helpVisitPumpUserList); helpVisitPumpStatus = new QAction(QIcon::fromTheme("network-server", QIcon(":/images/button-online.png")), tr("Pump.io &Network Status Website"), this); connect(helpVisitPumpStatus, SIGNAL(triggered()), this, SLOT(visitPumpStatus())); helpMenu->addAction(helpVisitPumpStatus); helpMenu->addSeparator(); // --- helpAbout = new QAction(QIcon(":/icon/64x64/dianara.png"), tr("About &Dianara"), this); connect(helpAbout, SIGNAL(triggered()), this, SLOT(aboutDianara())); helpMenu->addAction(helpAbout); this->menuBar()->addMenu(helpMenu); ///// Context menu for the tray icon trayTitleSeparatorAction = new QAction("Dianara", this); trayTitleSeparatorAction->setSeparator(true); trayShowWindowAction = new QAction(QIcon(":/icon/64x64/dianara.png"), "*show-window*", this); connect(trayShowWindowAction, SIGNAL(triggered()), this, SLOT(toggleMainWindow())); trayContextMenu = new QMenu("Tray Context Menu", this); trayContextMenu->setSeparatorsCollapsible(false); trayContextMenu->addAction(trayTitleSeparatorAction); // Acts as title trayContextMenu->addAction(trayShowWindowAction); trayContextMenu->addSeparator(); trayContextMenu->addAction(sessionUpdateMainTimeline); trayContextMenu->addAction(sessionUpdateMinorFeedMain); trayContextMenu->addAction(sessionAutoUpdates); trayContextMenu->addSeparator(); trayContextMenu->addAction(sessionMarkAllAsRead); trayContextMenu->addAction(sessionPostNote); trayContextMenu->addSeparator(); trayContextMenu->addAction(settingsEditProfile); trayContextMenu->addAction(settingsConfigure); trayContextMenu->addSeparator(); trayContextMenu->addAction(helpBasicHelp); trayContextMenu->addAction(helpAbout); trayContextMenu->addSeparator(); trayContextMenu->addAction(sessionQuit); // FIXME: if mainwindow is hidden, program quits // after closing Configure or About window (now partially fixed) qDebug() << "Menus created"; } void MainWindow::createToolbar() { this->mainToolBar = addToolBar(tr("Toolbar")); mainToolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle); mainToolBar->setMovable(false); mainToolBar->addAction(this->sessionUpdateMainTimeline); mainToolBar->addAction(this->sessionUpdateMinorFeedMain); mainToolBar->addAction(this->sessionMarkAllAsRead); mainToolBar->addSeparator(); this->settingsFilters->setPriority(QAction::LowPriority); // Don't show text in besides-icon mode mainToolBar->addAction(this->settingsFilters); this->settingsConfigure->setPriority(QAction::LowPriority); mainToolBar->addAction(this->settingsConfigure); mainToolBar->hide(); } /* * Avoid auto-creation of popup menus in the menu bar, tool bar, etc. * */ QMenu *MainWindow::createPopupMenu() { return NULL; } void MainWindow::createStatusbarWidgets() { this->initializationProgressBar = new QProgressBar(this); this->initializationProgressBar->setRange(0, 12); this->initializationProgressBar->setMaximumWidth(100); this->initializationProgressBar->hide(); connect(pumpController, SIGNAL(initializationStepChanged(int)), initializationProgressBar, SLOT(setValue(int))); this->statusStateButton = new QToolButton(this); this->setStateIcon(MainWindow::Initializing); connect(statusStateButton, SIGNAL(clicked()), sessionAutoUpdates, SLOT(toggle())); this->statusLogButton = new QToolButton(this); statusLogButton->setIcon(QIcon::fromTheme("text-x-log", QIcon(":/images/log.png"))); statusLogButton->setToolTip(tr("Open the log viewer")); connect(statusLogButton, SIGNAL(clicked()), viewLogAction, SLOT(trigger())); this->statusBar()->addPermanentWidget(initializationProgressBar); this->statusBar()->addPermanentWidget(statusLogButton); this->statusBar()->addPermanentWidget(statusStateButton); this->statusBar()->setSizeGripEnabled(false); } void MainWindow::setStateIcon(MainWindow::StatusType statusType) { if (statusType == Initializing) { statusStateButton->setDisabled(true); // Until initialization is done statusStateButton->setToolTip(tr("Initializing...")); statusStateButton->setIcon(QIcon::fromTheme("user-offline", QIcon(":/images/button-offline.png"))); } else if (statusType == Autoupdating) { statusStateButton->setEnabled(true); statusStateButton->setToolTip("" + tr("Auto-updating enabled")); statusStateButton->setIcon(QIcon::fromTheme("user-online", QIcon(":/images/button-online.png"))); } else // MainWindow::Stopped { statusStateButton->setEnabled(true); statusStateButton->setToolTip("" + tr("Auto-updating disabled")); statusStateButton->setIcon(QIcon::fromTheme("user-busy", QIcon(":/images/button-busy.png"))); } } /* * Create an icon in the system tray, define its contextual menu, etc. * */ void MainWindow::createTrayIcon() { trayIcon = new QSystemTrayIcon(this); if (trayIcon->isSystemTrayAvailable()) { trayIconAvailable = true; this->setTrayIconPixmap(); // Set icon for "no unread messages" initially this->setTitleAndTrayInfo(this->tabWidget->currentIndex()); // Catch clicks on icon connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayControl(QSystemTrayIcon::ActivationReason))); // clicking in a popup notification (balloon-type) will show the window connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(show())); // Set contextual menu for the icon trayIcon->setContextMenu(this->trayContextMenu); trayIcon->show(); qDebug() << "Tray icon created"; } else { trayIconAvailable = false; qDebug() << "System tray not available"; } } /* * Set the tray icon's pixmap, with number of unread messages, or nothing * */ void MainWindow::setTrayIconPixmap(int count, int highlightedCount) { if (!trayIconAvailable) { return; } if (count != -1 && highlightedCount != -1) // Valid counts were passed, update { this->trayCurrentNewCount = count; this->trayCurrentHLCount = highlightedCount; } QPixmap iconPixmap; switch (this->trayIconType) { case 0: // Default iconPixmap = QIcon(":/icon/32x32/dianara.png") .pixmap(32, 32) .scaled(32, 32); break; case 1: // system iconset, if available iconPixmap = QIcon::fromTheme("dianara", QIcon(":/icon/32x32/dianara.png")) .pixmap(32, 32) .scaled(32, 32); break; case 2: // Use your own avatar iconPixmap = this->avatarIconButton->icon() .pixmap(32, 32) .scaled(32, 32); break; case 3: // Custom icon iconPixmap = this->trayCustomPixmap; break; } // Paint the number of unread messages on top of the pixmap, if != 0 if (trayCurrentNewCount > 0) { // Draw a pseudo-shadow on the side first QPainter painter(&iconPixmap); if (trayCurrentHLCount > 0) { // Paint a shadow that covers the higher part too painter.drawPixmap(0, 0, 32, 32, QPixmap(":/images/tray-bg-high.png")); } else { // Shadow for the lower part only painter.drawPixmap(0, 0, 32, 32, QPixmap(":/images/tray-bg-low.png")); } QFont font; font.setPixelSize(16); font.setWeight(QFont::Black); // The number QString messagesCountString = QString("%1").arg(trayCurrentNewCount); QPen pen; pen.setBrush(Qt::white); painter.setFont(font); painter.setPen(pen); // Draw the number of new messages painter.drawText(0, 0, 32, 34, // End painting outside, at Y=34, to skip margins Qt::AlignRight | Qt::AlignBottom, messagesCountString); if (trayCurrentHLCount > 0) { // The other number messagesCountString = QString("%1").arg(trayCurrentHLCount); pen.setBrush(Qt::cyan); painter.setPen(pen); // Draw the number of highlighted messages painter.drawText(0, -2, // Start painting outside, at Y=-2, to avoid some margins 32, 32, Qt::AlignRight | Qt::AlignTop, messagesCountString); } } this->trayIcon->setIcon(iconPixmap); } /* * Load general program settings and state: size, position... * */ void MainWindow::loadSettings() { QSettings settings; firstRun = settings.value("firstRun", true).toBool(); if (firstRun) { qDebug() << "This is the first run"; } userID = settings.value("userID", "").toString(); this->setTitleAndTrayInfo(tabWidget->currentIndex()); // Main window state settings.beginGroup("MainWindow"); this->resize(settings.value("windowSize", QSize(740, 680)).toSize()); if (!firstRun) // So we should have a proper position saved { this->move(settings.value("windowPosition").toPoint()); } this->mainSplitter->restoreState(settings.value("mainSplitterState", mainSplitter->saveState()).toByteArray()); // Set childrenCollapsible to false AFTER loading state, to make sure state does not mess with it mainSplitter->setChildrenCollapsible(false); settings.endGroup(); // General program configuration this->updateConfigSettings(); // Load map of known post ID's from disk loadPostsEverSeen(); qDebug() << "Settings loaded"; } /* * Save general program settings and state: size, position... * */ void MainWindow::saveSettings() { QSettings settings; if (settings.isWritable()) { settings.setValue("firstRun", false); // General main window status settings.beginGroup("MainWindow"); settings.setValue("windowSize", this->size()); settings.setValue("windowPosition", this->pos()); settings.setValue("mainSplitterState", this->mainSplitter->saveState()); settings.setValue("viewSidePanel", this->viewSidePanel->isChecked()); settings.setValue("viewToolbar", this->viewToolbar->isChecked()); settings.setValue("viewStatusBar", this->viewStatusBar->isChecked()); settings.endGroup(); settings.sync(); qDebug() << "MainWindow settings saved:" << settings.fileName(); } else { qDebug() << "Error saving MainWindow settings to:" << settings.fileName() << "\n(disk full?)"; } this->savePostsEverSeen(); } /* * Set PumpController to ignore all SSL errors * */ void MainWindow::enableIgnoringSslErrors() { this->pumpController->setIgnoreSslErrors(true); } /* * Set PumpController's protocol schema to http:// * */ void MainWindow::enableNoHttpsMode() { this->pumpController->setNoHttpsMode(); } /* * Find a post in any of the main timelines; * * To be used to copy comments over to a cloned post * */ Post *MainWindow::findPostInTimelines(QString id, bool *ok) { QList postsInAllTimelines; postsInAllTimelines.append(mainTimeline->getPostsInTimeline()); postsInAllTimelines.append(directTimeline->getPostsInTimeline()); postsInAllTimelines.append(activityTimeline->getPostsInTimeline()); postsInAllTimelines.append(favoritesTimeline->getPostsInTimeline()); *ok = false; foreach (Post *singlePost, postsInAllTimelines) { if (singlePost->getObjectId() == id) { *ok = true; return singlePost; } } return NULL; } void MainWindow::loadPostsEverSeen() { QVariantMap postsEverSeen; QFile dataFile(this->dataDirectory + "/postsEverSeen.ids"); dataFile.open(QIODevice::ReadOnly); while (!dataFile.atEnd()) { QString line = dataFile.readLine(); QStringList splitLine = line.split(" | "); QString key = splitLine.first().trimmed(); QString value = splitLine.last().trimmed(); if (!key.isEmpty() && !value.isEmpty()) { postsEverSeen.insert(key, value); } } dataFile.close(); qDebug() << "Loaded" << postsEverSeen.keys().count() << "IDs from" << dataFile.fileName(); this->postIdsToStore = 0; pumpController->updatePostsEverSeen(postsEverSeen); } /* * Called when quitting, and also sometimes from notifyTimelineUpdate() * */ void MainWindow::savePostsEverSeen() { QVariantMap postsEverSeen = pumpController->getPostsEverSeen(); QFile dataFile(this->dataDirectory + "/postsEverSeen.ids"); dataFile.open(QIODevice::WriteOnly); foreach (QString key, postsEverSeen.keys()) { QByteArray line = key.toLocal8Bit(); line.append(" | "); line.append(postsEverSeen.value(key).toByteArray()); line.append("\n"); dataFile.write(line); } dataFile.close(); this->postIdsToStore = 0; } ////////////////////////////////////////////////////////////////////////////// /////////////////////////////////// SLOTS //////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /* * Update UserID string from signal emitted in AccountDialog * */ void MainWindow::updateUserID(QString newUserID) { this->userID = newUserID; // update window title and tray icon tooltip this->setTitleAndTrayInfo(tabWidget->currentIndex()); // Remove current user's name, id and avatar this->fullNameLabel->setText("--"); this->userIdLabel->setText("_@_"); this->userHometownLabel->setText("--"); avatarIconButton->setIcon(QIcon(QPixmap(":/images/no-avatar.png") .scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation))); // Hide the temporary account button from the status bar, if needed if (this->statusAccountButtonUsed) { this->statusAccountButton->hide(); } //////////////////////////////////////////// Restart initialization process this->initializationComplete = false; this->mainTimeline->clearTimeLineContents(); this->directTimeline->clearTimeLineContents(); this->activityTimeline->clearTimeLineContents(); this->favoritesTimeline->clearTimeLineContents(); this->meanwhileFeed->clearContents(); this->mentionsFeed->clearContents(); this->actionsFeed->clearContents(); this->pumpController->setUserCredentials(userID); this->fdNotifier->setCurrentUserId(userID); qDebug() << "UserID updated from AccountDialog:" << userID; } /* * Update settings changed from ConfigDialog() * */ void MainWindow::updateConfigSettings() { this->publisher->syncFromConfig(); QSettings settings; settings.beginGroup("Configuration"); this->updateInterval = settings.value("updateInterval", 5).toInt(); if (updateInterval < 2 || updateInterval > 99) { updateInterval = 5; // TMP validation of stored setting - FIXME // Should be loaded and validated by GlobalObject } this->updateTimer->setInterval(updateInterval * 1000 * 60); // min > msec this->pumpController->setPostsPerPageMain(globalObject->getPostsPerPageMain()); this->pumpController->setPostsPerPageOther(globalObject->getPostsPerPageOther()); this->tabsPosition = settings.value("tabsPosition", QTabWidget::North).toInt(); tabWidget->setTabPosition((QTabWidget::TabPosition)tabsPosition); this->tabsMovable = settings.value("tabsMovable", true).toBool(); tabWidget->setMovable(tabsMovable); int selectedProxyType = settings.value("proxyType", 0).toInt(); // 0 means "no proxy" QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy; if (selectedProxyType == 1) { proxyType = QNetworkProxy::Socks5Proxy; } else if (selectedProxyType == 2) { proxyType = QNetworkProxy::HttpProxy; } QString proxyHostname = settings.value("proxyHostname").toString(); int proxyPort = settings.value("proxyPort", 0).toInt(); bool proxyUseAuth = settings.value("proxyUseAuth", false).toBool(); QString proxyUser = settings.value("proxyUser").toString(); QByteArray proxyPassword = QByteArray::fromBase64(settings.value("proxyPassword").toByteArray()); this->pumpController->setProxyConfig(proxyType, proxyHostname, proxyPort, proxyUseAuth, proxyUser, QString::fromLocal8Bit(proxyPassword)); // Sync PumpController's ignore-SSL-for-images option this->pumpController->setIgnoreSslInImages(globalObject->getPostIgnoreSslInImages()); // Sync privacy options in PumpController this->pumpController->setSilentFollows(globalObject->getSilentFollows()); this->pumpController->setSilentLists(globalObject->getSilentLists()); this->pumpController->setSilentLikes(globalObject->getSilentLikes()); // System tray icon type and custom icon filename, if any this->trayIconType = settings.value("systrayIconType", 0).toInt(); QString trayIconFilename = settings.value("systrayCustomIconFN").toString(); this->trayCustomPixmap = QPixmap(trayIconFilename).scaled(32, 32); if (trayCustomPixmap.isNull()) // Custom icon image is gone or something { trayCustomPixmap = QPixmap(":/icon/32x32/dianara.png"); } this->setTrayIconPixmap(trayCurrentNewCount, trayCurrentHLCount); // Sync icon settings.endGroup(); qDebug() << "updateInterval updated:" << updateInterval << updateInterval*60000; qDebug() << "tabsPosition updated:" << tabsPosition << tabWidget->tabPosition(); qDebug() << "tabsMovable updated:" << tabsMovable; qDebug() << "tray icon type updated:" << trayIconType; } /* * Enable or disable some widgets, depending on whether the applicacion * is authorized or not * */ void MainWindow::toggleWidgetsByAuthorization(bool authorized) { this->sessionUpdateMainTimeline->setEnabled(authorized); this->sessionUpdateDirectTimeline->setEnabled(authorized); this->sessionUpdateActivityTimeline->setEnabled(authorized); this->sessionUpdateFavoritesTimeline->setEnabled(authorized); this->sessionUpdateMinorFeedMain->setEnabled(authorized); this->sessionUpdateMinorFeedDirect->setEnabled(authorized); this->sessionUpdateMinorFeedActivity->setEnabled(authorized); if (authorized) { // TODO FIXME } else { // TODO } } void MainWindow::onInitializationComplete() { this->initializationComplete = true; this->sessionAutoUpdates->setEnabled(true); if (this->sessionAutoUpdates->isChecked()) { this->setStateIcon(MainWindow::Autoupdating); } else { this->setStateIcon(MainWindow::Stopped); } this->initializationProgressBar->hide(); this->adjustTimelineSizes(); // One final adjustment } /* * Stuff executed 1 second after MainWindow is created * */ void MainWindow::postInit() { qDebug() << "postInit();"; this->adjustTimelineSizes(); // Ask for proxy password if needed if (this->pumpController->needsProxyPassword()) { QString proxyPassword = QInputDialog::getText(this, tr("Proxy password required"), tr("You have configured a " "proxy server with " "authentication, but the " "password is not set.") + "\n\n" + tr("Enter the password " "for your proxy server:"), QLineEdit::Password); // FIXME: if this is cancelled, nothing will be loaded until program restart this->pumpController->setProxyPassword(proxyPassword); } QSettings settings; if (settings.value("FirstRunWizard/showWizard", true).toBool()) { this->showFirstRunWizard(); } } /* * Control interaction with the system tray icon * */ void MainWindow::trayControl(QSystemTrayIcon::ActivationReason reason) { qDebug() << "Tray icon activation reason:" << reason; if (reason != QSystemTrayIcon::Context) // Simple "main button" click in icon { /* qDebug() << "trayControl()"; qDebug() << "isHidden?" << this->isHidden(); qDebug() << "isVisible?" << this->isVisible(); qDebug() << "isMinimized?" << this->isMinimized(); qDebug() << "hasFocus?" << this->hasFocus(); */ // Hide or show the main window if (this->isMinimized()) { // hide and show, because raise() wouldn't work this->hide(); this->showNormal(); qDebug() << "RAISING from minimized state"; } else { this->toggleMainWindow(); } } } /* * If FreeDesktop.org notifications are not available, * fall back to Qt's balloon ones * */ void MainWindow::showTrayFallbackMessage(QString title, QString message) { this->trayIcon->showMessage(title, message, QSystemTrayIcon::Information, 4000); // 4 secs } void MainWindow::updateProfileData(QString avatarUrl, QString fullName, QString hometown, QString bio, QString eMail) { QString bioTooltip = bio; if (!bio.isEmpty()) { bioTooltip.prepend(""); // make it rich text, so it gets wordwrap bioTooltip.replace("\n", "
"); // HTML newlines } else { bioTooltip = "" + tr("Your biography is empty") + ""; } this->fullNameLabel->setText(fullName); this->fullNameLabel->setToolTip(bioTooltip); this->userIdLabel->setText(this->userID); this->userIdLabel->setToolTip(bioTooltip); this->userHometownLabel->setText(hometown); this->userHometownLabel->setToolTip(bioTooltip); qDebug() << "Updated profile data from server:" << fullName << " @" << hometown; this->avatarURL = avatarUrl; qDebug() << "Own avatar URL:" << avatarURL; // Get local file name, which is stored in base64 hash form QString avatarFilename = MiscHelpers::getCachedAvatarFilename(avatarURL); if (QFile::exists(avatarFilename)) { // Load avatar if already cached this->avatarIconButton->setIcon(QIcon(QPixmap(avatarFilename) .scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation))); qDebug() << "Using cached avatar for user"; } else { pumpController->getAvatar(avatarURL); } this->avatarIconButton->setToolTip(bioTooltip + "

" "" + tr("Click to edit your profile") + ""); // Fill/update this info in the profile editor too this->profileEditor->setProfileData(avatarURL, fullName, hometown, bio, eMail); // Refresh tray icon without changing unread/highlighted counts this->setTrayIconPixmap(-1, -1); } /* * Enable or disable the timer that auto-updates the timelines * */ void MainWindow::toggleAutoUpdates(bool checked) { QString message; if (checked) { this->setStateIcon(MainWindow::Autoupdating); this->updateTimer->start(); message = tr("Starting automatic update of timelines, " "once every %1 minutes.").arg(this->updateInterval); } else { this->setStateIcon(MainWindow::Stopped); this->updateTimer->stop(); message = tr("Stopping automatic update of timelines."); } this->setStatusBarMessage(message); this->logViewer->addToLog(message); } /* * Update all timelines * */ void MainWindow::updateAllTimelines() { mainTimeline->goToFirstPage(); // received timeline will come in a SIGNAL() directTimeline->goToFirstPage(); activityTimeline->goToFirstPage(); favoritesTimeline->goToFirstPage(); meanwhileFeed->updateFeed(); mentionsFeed->updateFeed(); actionsFeed->updateFeed(); qDebug() << "Updated all timelines by menu"; } /* * Update Main timeline and Meanwhile feed * * Those, in turn, might update the Messages timeline and the Mentions feed * */ void MainWindow::updateMainAndMinorTimelines() { mainTimeline->goToFirstPage(); meanwhileFeed->updateFeed(); qDebug() << "Updated some timelines automatically after:" << this->updateInterval << "min"; } /* * Update relevant feeds that need to reflect user's own activity. * * Called after posting. * */ void MainWindow::updateMainActivityMinorTimelines() { mainTimeline->goToFirstPage(); activityTimeline->goToFirstPage(); meanwhileFeed->updateFeed(); userDidSomethingTimer->start(); // Call updateActionsFeed() a little later qDebug() << "Updated some timelines after posting"; } /* * Update the Favorites timeline. Called by timer some time after the user * liked or unliked a post, unless they keep liking more stuff. * */ void MainWindow::updateFavoritesTimeline() { favoritesTimeline->goToFirstPage(); } /* * Update the Actions feed. Called by timer some time after the user * does something minor, unless they keep doing more things. * */ void MainWindow::updateActionsFeed() { actionsFeed->updateFeed(); // Stop it specifically, for cases when this is called from the menu this->userDidSomethingTimer->stop(); } void MainWindow::scrollMainTimelineTo(QAbstractSlider::SliderAction sliderAction) { this->mainTimelineScrollArea->verticalScrollBar()->triggerAction(sliderAction); // this->adjustTimelineSizes(); } void MainWindow::scrollDirectTimelineTo(QAbstractSlider::SliderAction sliderAction) { this->directTimelineScrollArea->verticalScrollBar()->triggerAction(sliderAction); } void MainWindow::scrollActivityTimelineTo(QAbstractSlider::SliderAction sliderAction) { this->activityTimelineScrollArea->verticalScrollBar()->triggerAction(sliderAction); } void MainWindow::scrollFavoritesTimelineTo(QAbstractSlider::SliderAction sliderAction) { this->favoritesTimelineScrollArea->verticalScrollBar()->triggerAction(sliderAction); } /* * Scroll timelines to make sure the commenter block of the post * currently being commented is shown * */ void MainWindow::scrollMainTimelineToWidget(QWidget *widget) { this->mainTimelineScrollArea->ensureWidgetVisible(widget, 1, 100); } void MainWindow::scrollDirectTimelineToWidget(QWidget *widget) { this->directTimelineScrollArea->ensureWidgetVisible(widget, 1, 100); } void MainWindow::scrollActivityTimelineToWidget(QWidget *widget) { this->activityTimelineScrollArea->ensureWidgetVisible(widget, 1, 100); } void MainWindow::scrollFavoritesTimelineToWidget(QWidget *widget) { this->favoritesTimelineScrollArea->ensureWidgetVisible(widget, 1, 100); } /* * Scroll to new stuff separator line widget (delayed, called from a timer) * */ void MainWindow::scrollToNewPosts() { this->scrollMainTimelineToWidget(mainTimeline->getSeparatorFrame()); } void MainWindow::notifyTimelineUpdate(PumpController::requestTypes timelineType, int newPostCount, int highlightCount, int deletedPostCount, int filteredPostCount, int pendingPostCount) { /* Stop auto-updates timer to postpone the auto-updates each time a manual * update is triggered (including moving to a different page). * */ this->updateTimer->stop(); bool loadedOlderPage = false; // Both of these set to -1 means it's an older page if (highlightCount == -1 && pendingPostCount == -1) { loadedOlderPage = true; } // Update postsEverSeen on disk, when there are enough useful IDs pending if (newPostCount > 0 && !loadedOlderPage) { this->postIdsToStore += newPostCount; // Some might already be in the file, but still... if (postIdsToStore > 10) { this->savePostsEverSeen(); // This sets postIdsToStore = 0 } } // Restart auto-updates timer ONLY when autoupdates are enabled if (this->sessionAutoUpdates->isChecked()) { this->updateTimer->start(); /* * FIXME 1.3.3: * * autoupdates won't actually update anything unless we're at the top * of the first page; instead, some sort of message will be displayed * about it, but no content will be changed. * * This way, re-starting the timer won't be necessary. * */ } QString timelineName = PumpController::getFeedNameAndPath(timelineType).first(); // First handle the simpler case, where the loaded posts are from an older page if (loadedOlderPage) { this->setStatusBarMessage(tr("Received %1 older posts in '%2'.", "%1 is a number, %2 = name of a timeline") .arg(newPostCount) .arg(timelineName)); return; } QString statusBarMessage = tr("'%1' updated.", "%1 is the name of a feed") .arg(timelineName); if (newPostCount > 0) { QString timelineUpdatedAt = tr("Timeline updated at %1.") .arg(QTime::currentTime().toString()); // How many NEW QString newPostsString; if (newPostCount == 1) { newPostsString = tr("There is 1 new post."); } else { newPostsString = tr("There are %1 new posts.").arg(newPostCount); } statusBarMessage.append(" " + newPostsString); // How many of those NEW are HIGHLIGHTED QString hlPostsString; if (highlightCount > 0) { if (highlightCount == 1) { hlPostsString = tr("1 highlighted.", "singular, refers to a post"); } else { hlPostsString = tr("%1 highlighted.", "plural, refers to posts").arg(highlightCount); } statusBarMessage.append(" " + hlPostsString); } // How many more PENDING to receive next time QString pendingPostsString; if (pendingPostCount > 0) { if (pendingPostCount == 1) { pendingPostsString = tr("1 more pending to receive.", "singular, one post"); } else { pendingPostsString = tr("%1 more pending to receive.", "plural, several posts") .arg(pendingPostCount); } statusBarMessage.append(" " + pendingPostsString); } // Also: how many are deleted or filtered out if (filteredPostCount > 0 || deletedPostCount > 0) { statusBarMessage.append(" " + tr("Also:")); QString filteredPostsString; if (filteredPostCount > 0) { if (filteredPostCount == 1) { filteredPostsString = tr("1 filtered out.", "singular, refers to a post"); } else { filteredPostsString = tr("%1 filtered out.", "plural, refers to posts") .arg(filteredPostCount); } statusBarMessage.append(" " + filteredPostsString); } QString deletedPostsString; if (deletedPostCount > 0) { if (deletedPostCount == 1) { deletedPostsString = tr("1 deleted.", "singular, refers to a post"); } else { deletedPostsString = tr("%1 deleted.", "plural, refers to posts") .arg(deletedPostCount); } statusBarMessage.append(" " + deletedPostsString); } } this->setStatusBarMessage(statusBarMessage); // Only for the main timeline if (timelineType == PumpController::MainTimelineRequest) { if (fdNotifier->getNotifyHLTimeline() && highlightCount > 0) { fdNotifier->showMessage(timelineUpdatedAt + "\n\n" + newPostsString + "\n" + hlPostsString + "\n" + pendingPostsString); } else if (fdNotifier->getNotifyNewTimeline()) { fdNotifier->showMessage(timelineUpdatedAt + "\n\n" + newPostsString + "\n" + pendingPostsString); } this->logViewer->addToLog(statusBarMessage); // Jump to new stuff separator, if enabled if (this->globalObject->getJumpToNewOnUpdate() && this->initializationComplete) // Not for the first load! { /* Timer will jump in one second, after events have been * processed, and widgets have been adjusted */ this->delayedScrollTimer->start(1000); } } } else { if (timelineType == PumpController::MainTimelineRequest || timelineType == PumpController::DirectTimelineRequest) { // Only add 'no new posts' for timelines that can have unread posts statusBarMessage.append(QString::fromUtf8(" \342\210\205 ") // Empty set + tr("No new posts.")); } this->setStatusBarMessage(statusBarMessage); } // And adjust timelines sizes -- NOW DISABLING: makes reloads // a lot slower for no good reason - this->adjustTimelineSizes(); if (timelineType == PumpController::MainTimelineRequest) { // Some highlighted could mean some new direct messages, so update that timeline if (highlightCount > 0 || pendingPostCount > 0) // Also if more than max new posts { ////// FIXME: should only do this if one of the highlighted is actually a direct message if (initializationComplete) // But not the first time { // Store statusBarMessage, to restore after the Messages timeline update this->previousStatusMessageFromTimeline = statusBarMessage; this->directTimeline->goToFirstPage(); } } // Also, update menu info label with current time this->menuInfoWidget->hide(); // Hide info widget before updating label // to ensure it gets the proper size this->menuInfoLabel->setText(tr("Last update: %1") .arg(QTime::currentTime().toString())); // Show again, but only if the menu bar is not native (not handled by environment) this->menuInfoWidget->setVisible(!this->menuBar()->isNativeMenuBar()); } else { /* * Updated other timeline, and we have a previous statusbar message, * which means this is an auto-update. * If this update didn't bring anything new, we'll restore the previous * status message, which might have been more interesting * */ if (newPostCount == 0 && !this->previousStatusMessageFromTimeline.isEmpty()) { this->setStatusBarMessage(this->previousStatusMessageFromTimeline); } this->previousStatusMessageFromTimeline.clear(); // Empty for next time } } /* * Update timelines titles with number of new posts * */ void MainWindow::setTimelineTabTitle(PumpController::requestTypes timelineType, int newPostCount, int highlightCount, int totalFeedPostCount) { int updatedTab = 0; QString messageCountString; if (newPostCount > 0) { messageCountString = QString(" (%1)").arg(newPostCount); if (highlightCount > 0 && timelineType == PumpController::MainTimelineRequest) { // Showing count of highlighted posts makes sense in the main TL only messageCountString.append(QString(" [%1]").arg(highlightCount)); } } QString totalItemsString = "\n\n" + tr("Total posts: %1") .arg(QLocale::system() .toString(totalFeedPostCount)); switch (timelineType) { case PumpController::MainTimelineRequest: this->tabWidget->setTabText(0, tr("&Timeline") + messageCountString); this->tabWidget->setTabToolTip(0, tr("The main timeline") + totalItemsString); updatedTab = 0; break; case PumpController::DirectTimelineRequest: this->tabWidget->setTabText(1, tr("&Messages") + messageCountString); this->tabWidget->setTabToolTip(1, tr("Messages sent explicitly to you") + totalItemsString); updatedTab = 1; break; case PumpController::ActivityTimelineRequest: this->tabWidget->setTabText(2, tr("&Activity") + messageCountString); this->tabWidget->setTabToolTip(2, tr("Your own posts") + totalItemsString); updatedTab = 2; break; case PumpController::FavoritesTimelineRequest: this->tabWidget->setTabText(3, tr("Favor&ites") + messageCountString); this->tabWidget->setTabToolTip(3, tr("Your favorited posts") + totalItemsString); updatedTab = 3; break; default: updatedTab = -1; qDebug() << "setTimelineTabTitle() called with wrong feed type!"; } // If the updated tab is the current one, set also window title, etc. if (updatedTab == tabWidget->currentIndex()) { this->setTitleAndTrayInfo(updatedTab); } // If it's the main timeline, set the tray icon pixmap with the newmsg count if (updatedTab == 0) { this->setTrayIconPixmap(newPostCount, highlightCount); } } /* * Set mainWindow's title based on current tab, and user ID * * Use that same title as tray icon's tooltip * */ void MainWindow::setTitleAndTrayInfo(int currentTab) { QString currentTabTitle; currentTabTitle = this->tabWidget->tabText(currentTab); currentTabTitle.remove("&"); // Remove accelators QString title = currentTabTitle; if (!this->userID.isEmpty()) { title.append(" - " + userID); } title.append(" - Dianara"); /* Not sure if I like it if (currentTabTitle.endsWith(")")) // kinda TMP { title.prepend("* "); } */ this->setWindowTitle(title); if (trayIconAvailable) { QString idToShow = this->userID.isEmpty() ? tr("Your Pump.io account is not configured") : userID; #ifdef Q_OS_UNIX title = "Dianara: " + currentTabTitle + "

[" + idToShow + "]"; #else // Some OSes don't render HTML in the tray tooltip title = "Dianara: " + currentTabTitle + "\n\n[" + idToShow + "]"; #endif this->trayIcon->setToolTip(title); this->trayTitleSeparatorAction->setText("Dianara - " + this->userID); } } /* * Set the title for the minor feed (Meanwhile), with new item count * */ void MainWindow::setMinorFeedTitle(int newItemsCount, int newHighlightedItemsCount) { QString title = PumpController::getFeedNameAndPath(PumpController::MinorFeedMainRequest) .first() + "..."; if (newItemsCount > 0) { title.append(QString(" (%1)").arg(newItemsCount)); if (newHighlightedItemsCount > 0) { title.append(QString(" [%1]").arg(newHighlightedItemsCount)); } } this->leftPanel->setItemText(0, title); } void MainWindow::setMentionsFeedTitle(int newItemsCount, int newHighlightedItemsCount) { Q_UNUSED(newHighlightedItemsCount) QString title = PumpController::getFeedNameAndPath(PumpController::MinorFeedDirectRequest) .first(); if (newItemsCount > 0) { title.append(QString(" (%1)").arg(newItemsCount)); } this->leftPanel->setItemText(1, title); } void MainWindow::notifyMinorFeedUpdate(PumpController::requestTypes feedType, int newItemsCount, int highlightedCount, int filteredCount, int pendingItemsCount) { QString feedName = PumpController::getFeedNameAndPath(feedType).first(); // First, handle the simpler case, where items are older if (highlightedCount == -1 && pendingItemsCount == -1) { // When both of these are -1 this->setStatusBarMessage(tr("Received %1 older activities in '%2'.", "%1 is a number, %2 = name of feed") .arg(newItemsCount) .arg(feedName)); return; } QString statusBarMessage = tr("'%1' updated.", "%1 is the name of a feed").arg(feedName); if (newItemsCount > 0) { QString mwUpdatedAt = tr("Minor feed updated at %1.") .arg(QTime::currentTime().toString()); // How many NEW items QString newItemsString; if (newItemsCount == 1) { newItemsString = tr("There is 1 new activity."); } else { newItemsString = tr("There are %1 new activities.") .arg(newItemsCount); } statusBarMessage.append(" " + newItemsString); // How many of those NEW items are highlighted QString hlItemsString; if (highlightedCount > 0) { if (highlightedCount == 1) { hlItemsString = tr("1 highlighted.", "singular, refers to an activity"); } else { hlItemsString = tr("%1 highlighted.", "plural, refers to activities") .arg(highlightedCount); } statusBarMessage.append(" " + hlItemsString); } // How many more items are PENDING to receive on next update QString pendingItemsString; if (pendingItemsCount > 0) { if (pendingItemsCount == 1) { pendingItemsString = tr("1 more pending to receive.", "singular, 1 activity"); } else { pendingItemsString = tr("%1 more pending to receive.", "plural, several activities") .arg(pendingItemsCount); } statusBarMessage.append(" " + pendingItemsString); } // Also: how many are filtered out QString filteredItemsString; if (filteredCount > 0) { statusBarMessage.append(" " + tr("Also:")); if (filteredCount == 1) { filteredItemsString = tr("1 filtered out.", "singular, refers to one activity"); } else { filteredItemsString = tr("%1 filtered out.", "plural, several activities") .arg(filteredCount); } statusBarMessage.append(" " + filteredItemsString); } // Some stuff is only done for the Meanwhile feed: HL counts, notifications... if (feedType == PumpController::MinorFeedMainRequest) { if (highlightedCount > 0) { if (fdNotifier->getNotifyHLMeanwhile()) { fdNotifier->showMessage(mwUpdatedAt + "\n\n" + newItemsString + "\n" + hlItemsString + "\n" + pendingItemsString); } } else { if (fdNotifier->getNotifyNewMeanwhile()) { fdNotifier->showMessage(mwUpdatedAt + "\n\n" + newItemsString + "\n" + pendingItemsString); } } // Some highlighted in the Meanwhile could mean some new mentions, so update that feed if (highlightedCount > 0 || pendingItemsCount > 0) // Also if more than max new activities { if (initializationComplete) // But not the first time { // Store statusBarMessage, and restore after the Mentions and // Actions feeds are updated, if they have nothing new this->previousStatusMessageFromMinorFeed = statusBarMessage; this->mentionsFeed->updateFeed(); } } } this->setStatusBarMessage(statusBarMessage); this->logViewer->addToLog(statusBarMessage); } else { if (feedType != PumpController::MinorFeedActivityRequest) { // Don't add redundant 'no activities' for Actions feed statusBarMessage.append(QString::fromUtf8(" \342\210\205 ") // Empty set + tr("No new activities.")); } // For Mentions/Actions, use previously stored useful message, if any if (feedType != PumpController::MinorFeedMainRequest && !this->previousStatusMessageFromMinorFeed.isEmpty()) { statusBarMessage = previousStatusMessageFromMinorFeed; previousStatusMessageFromMinorFeed.clear(); } this->setStatusBarMessage(statusBarMessage); } } /* * Store avatars on disk * */ void MainWindow::storeAvatar(QByteArray avatarData, QString avatarUrl) { QString fileName = MiscHelpers::getCachedAvatarFilename(avatarUrl); qDebug() << "Saving avatar to disk: " << fileName; QFile avatarFile(fileName); avatarFile.open(QFile::WriteOnly); avatarFile.write(avatarData); avatarFile.close(); this->pumpController->notifyAvatarStored(avatarUrl, avatarFile.fileName()); if (avatarUrl == this->avatarURL) { this->avatarIconButton->setIcon(QIcon(QPixmap(avatarFile.fileName()) .scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation))); } qDebug() << "avatarData size:" << avatarData.size(); } /* * Store images on disk * */ void MainWindow::storeImage(QByteArray imageData, QString imageUrl) { QString fileName = MiscHelpers::getCachedImageFilename(imageUrl); QFile imageFile(fileName); bool fileOpenedOk = imageFile.open(QFile::WriteOnly); imageFile.write(imageData); imageFile.close(); if (fileOpenedOk && imageFile.size() > 0) { qDebug() << "Saving image to disk: " << fileName << "; File opened OK? " << fileOpenedOk; this->pumpController->notifyImageStored(imageUrl); } else { qDebug() << "Couldn't save" << fileName << "to disk!"; QString message = tr("Error storing image!") + " (" + tr("%1 bytes").arg(imageData.size()) + ")"; this->setStatusBarMessage(message); logViewer->addToLog(message); if (fileOpenedOk) // Opened OK but no disk space, or something { imageFile.remove(); } this->pumpController->notifyImageFailed(imageUrl); } qDebug() << "imageData size:" << imageData.size(); } /* * Update status bar message, from PumpController's signals * */ void MainWindow::setStatusBarMessage(QString message) { this->oldStatusBarMessage = "[" + QTime::currentTime().toString() + "] " + message; this->statusBar()->showMessage(oldStatusBarMessage, 0); } /* * Show a temporary message in the statusBar * * If message is empty, restore the old one * */ void MainWindow::setTransientStatusMessage(QString message) { if (!message.isEmpty()) { if (message.startsWith("http")) { message = tr("Link to: %1").arg(message); } this->statusBar()->showMessage(message, 0); } else { // Old status message was saved in setStatusBarMessage() this->statusBar()->showMessage(oldStatusBarMessage, 0); } } /* * Mark all posts in all timelines as read * and clear their counters * */ void MainWindow::markAllAsRead() { this->setTransientStatusMessage(tr("Marking everything as read...")); qApp->processEvents(); mainTimeline->markPostsAsRead(); directTimeline->markPostsAsRead(); // activityTimeline and favoritesTimeline don't need this; they're never "unread" meanwhileFeed->markAllAsRead(); this->setMinorFeedTitle(0, 0); mentionsFeed->markAllAsRead(); this->setMentionsFeedTitle(0, 0); // activitiesFeed doesn't need this, either this->setTransientStatusMessage(""); // Restore } void MainWindow::startPost(QString title, QString content) { if (this->isHidden()) { this->toggleMainWindow(); } this->publisher->setFullMode(); // Don't try setting title and content if called via menu, only via D-Bus if (!title.isEmpty() || !content.isEmpty()) { this->publisher->setTitleAndContent(title, content); } } /* * Refresh all timestamps in the minor feed, in timelines posts, * and in comments * */ void MainWindow::refreshAllTimestamps() { this->mainTimeline->updateFuzzyTimestamps(); this->directTimeline->updateFuzzyTimestamps(); this->activityTimeline->updateFuzzyTimestamps(); this->favoritesTimeline->updateFuzzyTimestamps(); this->meanwhileFeed->updateFuzzyTimestamps(); this->mentionsFeed->updateFuzzyTimestamps(); this->actionsFeed->updateFuzzyTimestamps(); } /* * Adjust timelines maximum widths according to their scrollareas sizes, * which in turn depend on the window size * * Called from the resizeEvent(), among other places * */ void MainWindow::adjustTimelineSizes() { int timelineWidth; int timelineHeight; // Get the right timelinewidth based on currently active tab's timeline width switch (this->tabWidget->currentIndex()) { case 0: // main timelineWidth = mainTimelineScrollArea->viewport()->width(); timelineHeight = mainTimelineScrollArea->viewport()->height(); break; case 1: // direct timelineWidth = directTimelineScrollArea->viewport()->width(); timelineHeight = directTimelineScrollArea->viewport()->height(); break; case 2: // activity timelineWidth = activityTimelineScrollArea->viewport()->width(); timelineHeight = activityTimelineScrollArea->viewport()->height(); break; case 3: // favorites timelineWidth = favoritesTimelineScrollArea->viewport()->width(); timelineHeight = favoritesTimelineScrollArea->viewport()->height(); break; default: // Contacts tab, ATM timelineWidth = contactManager->width(); //->visibleRegion().boundingRect().width(); //->width() - scrollbarWidth; timelineHeight = contactManager->height(); } // Then set the maximum width for all of them based on that mainTimeline->setMaximumWidth(timelineWidth); directTimeline->setMaximumWidth(timelineWidth); activityTimeline->setMaximumWidth(timelineWidth); favoritesTimeline->setMaximumWidth(timelineWidth); // Some basic 1:2 proportions TMP FIXME if (timelineHeight > (timelineWidth * 2)) { timelineHeight = timelineWidth * 2; } this->globalObject->storeTimelineHeight(timelineHeight); // Resize switch (this->tabWidget->currentIndex()) { case 0: // main mainTimeline->resizePosts(QList(), true); // resizeAll=true break; case 1: // direct directTimeline->resizePosts(QList(), true); break; case 2: // activity activityTimeline->resizePosts(QList(), true); break; case 3: // favorites favoritesTimeline->resizePosts(QList(), true); break; default: // Contacts tab, ATM break; } } void MainWindow::showUserTimeline(QString userId, QString userName, QIcon userAvatar, QString userOutbox) { UserPosts *userTimeline = new UserPosts(userId, userName, userAvatar, userOutbox, this->pumpController, this->globalObject, this->filterChecker, this); userTimeline->show(); } /* * Hide or show the side panel * */ void MainWindow::toggleSidePanel(bool shown) { // Unless the publisher (really, the composer) has focus already, // give focus to timeline before, to avoid giving focus to the publisher if (!this->publisher->hasFocus()) { // FIXME: check if publisher's composer has focus! this->mainTimeline->setFocus(); } qDebug() << "Showing side panel:" << shown; this->leftSideWidget->setVisible(shown); if (!shown) { qApp->processEvents(); this->adjustTimelineSizes(); } } void MainWindow::toggleToolbar(bool shown) { qDebug() << "Showing toolbar:" << shown; this->mainToolBar->setVisible(shown); } /* * Hide or show the status bar * */ void MainWindow::toggleStatusBar(bool shown) { qDebug() << "Showing status bar:" << shown; this->statusBar()->setVisible(shown); } /* * Toggle between fullscreen mode and normal window mode * */ void MainWindow::toggleFullscreen(bool enabled) { if (enabled) { this->showFullScreen(); } else { this->showNormal(); } } void MainWindow::toggleMeanwhileFeed() { this->leftPanel->setCurrentIndex(0); this->meanwhileFeed->setFocus(); } void MainWindow::toggleMentionsFeed() { this->leftPanel->setCurrentIndex(1); this->mentionsFeed->setFocus(); } void MainWindow::toggleActionsFeed() { this->leftPanel->setCurrentIndex(2); this->actionsFeed->setFocus(); } void MainWindow::showFirstRunWizard() { ////// FIXME //// TESTS FirstRunWizard *tmpWizard = new FirstRunWizard(this->accountDialog, this->profileEditor, this->configDialog, this->helpWidget, this->globalObject, this); tmpWizard->show(); ////// FIXME //// } /* * Open website in browser * */ void MainWindow::visitWebSite() { qDebug() << "Opening website in browser"; QDesktopServices::openUrl(QUrl("https://jancoding.wordpress.com/dianara")); } void MainWindow::visitBugTracker() { qDebug() << "Opening bugtracker in browser"; QDesktopServices::openUrl(QUrl("https://gitlab.com/dianara/dianara-dev/issues")); } /* * Open Pump.io's User Guide in web browser * (currently at readthedocs.org) * */ void MainWindow::visitPumpGuide() { qDebug() << "Opening Pump.io User Guide in browser"; QDesktopServices::openUrl(QUrl("https://pumpio.readthedocs.org/en/latest/userguide.html")); } void MainWindow::visitTips() { qDebug() << "Opening Pump.io tips in browser"; QDesktopServices::openUrl(QUrl("http://communicationfreedom.wordpress.com/2014/03/17/pump-io-tips/")); } void MainWindow::visitUserList() { qDebug() << "Opening Pump.io users-by-language wiki page in browser"; QDesktopServices::openUrl(QUrl("https://github.com/e14n/pump.io/wiki/Users-by-language")); } void MainWindow::visitPumpStatus() { qDebug() << "Opening Pump.io Network Status website in browser"; QDesktopServices::openUrl(QUrl("https://pumpiostatus.website")); } /* * About... message * */ void MainWindow::aboutDianara() { QMessageBox::about(this, tr("About Dianara"), QString("Dianara v%1") .arg(qApp->applicationVersion()) + "
" "Copyright 2012-2015 JanKusanagi

" "" "https://jancoding.wordpress.com/dianara
" "
" "
" + tr("Dianara is a pump.io social networking client.") + "

" + tr("With Dianara you can see your timelines, " "create new posts, upload pictures and " "other media, interact with posts, manage " "your contacts and follow new people.") + "
" "
" // --- "
" + tr("English translation by JanKusanagi.", "TRANSLATORS: Change this with your language and name. " "If there was another translator before you, add your " "name after theirs ;)") + "
" "
" + tr("Thanks to all the testers, translators and packagers, " "who help make Dianara better!") + "
" "
" // --- "
" + tr("Dianara is licensed under the GNU GPL license, and " "uses some Oxygen icons: http://www.oxygen-icons.org/ " "(LGPL licensed)") + "

" "" "GNU GPL v2 - " "" "GNU LGPL v2.1"); if (this->isHidden()) // FIXME: ugly workaround to avoid closing the program { // after closing About dialog, if mainWindow hidden this->show(); this->hide(); // This hack might be causing problems under LXDE qDebug() << "MainWindow was hidden, showing and hiding again"; } } void MainWindow::toggleMainWindow(bool firstTime) { bool shouldHide = false; if (firstTime) { shouldHide = this->globalObject->getHideInTray(); if (shouldHide) { qDebug() << "Hiding window on startup due to configuration"; } } if ((this->isHidden() && !shouldHide) || !this->trayIconAvailable) { this->trayShowWindowAction->setText(tr("&Hide Window")); this->show(); qDebug() << "SHOWING main window"; // Call adjustTimelineSizes() half a second later delayedResizeTimer->stop(); delayedResizeTimer->start(500); } else { this->trayShowWindowAction->setText(tr("&Show Window")); this->hide(); qDebug() << "HIDING main window"; } } void MainWindow::showAuthError(QString title, QString message) { QMessageBox::warning(this, title, message); } void MainWindow::onSessionManagerQuitRequest(QSessionManager &manager) { Q_UNUSED(manager) std::cout << "Session manager requested shutdown\n"; std::cout.flush(); this->reallyQuitProgram = true; this->quitProgram(tr("Closing due to environment shutting down...")); } /* * Close the program. Needed to quit correctly from context menu * */ void MainWindow::quitProgram(QString reason) { if (this->isHidden()) { this->show(); } saveSettings(); if (!reallyQuitProgram && this->publisher->isFullMode()) // If composer is active, ask for confirmation { // FIXME: composing comments should also block here int confirmation = QMessageBox::question(this, tr("Quit?"), tr("You are composing a note or a comment.") + "\n\n" + tr("Do you really want to close Dianara?"), tr("&Yes, close the program"), tr("&No"), "", 1, 1); if (confirmation == 0) { qDebug() << "Exit confirmed; quitting, bye!"; reallyQuitProgram = true; } else { qDebug() << "Confirmation cancelled, not exiting"; reallyQuitProgram = false; } } else // FIXME: some redundancies here... { reallyQuitProgram = true; } if (reallyQuitProgram) { if (reason.isEmpty()) { reason = tr("Shutting down Dianara..."); } this->setStatusBarMessage(reason); logViewer->addToLog(reason); std::cout << "\nShutting down Dianara (" + this->userID.toStdString() + ")...\n"; std::cout.flush(); // Manually remove some stuff; trying to minimize shutdown time this->mainTimeline->clearTimeLineContents(false); // Don't show 'loading' this->directTimeline->clearTimeLineContents(false); this->activityTimeline->clearTimeLineContents(false); this->favoritesTimeline->clearTimeLineContents(false); this->meanwhileFeed->clearContents(); this->mentionsFeed->clearContents(); this->actionsFeed->clearContents(); this->publisher->deleteLater(); this->contactManager->deleteLater(); this->logViewer->close(); this->helpWidget->close(); // Add more needed shutdown stuff here qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers); qApp->closeAllWindows(); //qApp->processEvents(); std::cout << "All windows closed, bye! o/\n"; std::cout.flush(); } } ////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// PROTECTED ////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void MainWindow::closeEvent(QCloseEvent *event) { qDebug() << "MainWindow::closeEvent()"; // FIXME: handle cases where tray icon isn't available // Handle also really closing via environment shutdown if (!trayIconAvailable && !reallyQuitProgram) { int confirmation = QMessageBox::question(this, tr("Quit?"), tr("System tray icon is not available.") + " " + tr("Dianara cannot be hidden in the " "system tray.") + "\n\n" + tr("Do you want to close the program " "completely?"), tr("&Yes, close the program"), tr("&No"), "", 1, 1); if (confirmation == 0) { qDebug() << "Main window closed without a tray icon, shutting down"; reallyQuitProgram = true; this->quitProgram(); } else { qDebug() << "Confirmation cancelled, not closing main window"; event->ignore(); return; } } if (reallyQuitProgram) { event->accept(); // really close, if called from Quit menu qDebug() << "Quit called from menu, shutting down program"; } else { this->toggleMainWindow(); // Hide window, app accessible via tray icon qDebug() << "Tried to close main window, so hiding to tray"; event->ignore(); // ignore the closeEvent } } void MainWindow::resizeEvent(QResizeEvent *event) { qDebug() << "MainWindow::resizeEvent()" << event->size(); delayedResizeTimer->stop(); delayedResizeTimer->start(500); // call adjustTimelineSizes() a little later event->accept(); } dianara-v1.3.2/src/mainwindow.h000644 000764 000764 00000024222 12604507671 016022 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #endif #include #include #include #include #ifdef QT_DBUS_LIB #include #include "dbusinterface.h" #endif #include #include #include "accountdialog.h" #include "configdialog.h" #include "pumpcontroller.h" #include "notifications.h" #include "publisher.h" #include "timeline.h" #include "post.h" #include "contactmanager.h" #include "minorfeed.h" #include "profileeditor.h" #include "filtereditor.h" #include "filterchecker.h" #include "logviewer.h" #include "helpwidget.h" #include "groupsmanager.h" #include "globalobject.h" #include "userposts.h" #include "firstrunwizard.h" class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); void prepareDataDirectory(); void createMenus(); void createToolbar(); virtual QMenu *createPopupMenu(); void createStatusbarWidgets(); enum StatusType { Initializing, Autoupdating, Stopped }; void setStateIcon(StatusType statusType); void createTrayIcon(); void setTrayIconPixmap(int newCount = 0, int highlightedCount = 0); void loadSettings(); void saveSettings(); void enableIgnoringSslErrors(); void enableNoHttpsMode(); Post *findPostInTimelines(QString id, bool *ok); void loadPostsEverSeen(); void savePostsEverSeen(); public slots: void updateUserID(QString newUserID); void updateConfigSettings(); void toggleWidgetsByAuthorization(bool authorized); void onInitializationComplete(); void postInit(); void trayControl(QSystemTrayIcon::ActivationReason reason); void showTrayFallbackMessage(QString title, QString message); void updateProfileData(QString avatarUrl, QString fullName, QString hometown, QString bio, QString eMail); void toggleAutoUpdates(bool checked); void updateAllTimelines(); void updateMainAndMinorTimelines(); void updateMainActivityMinorTimelines(); void updateFavoritesTimeline(); void updateActionsFeed(); void scrollMainTimelineTo(QAbstractSlider::SliderAction sliderAction); void scrollDirectTimelineTo(QAbstractSlider::SliderAction sliderAction); void scrollActivityTimelineTo(QAbstractSlider::SliderAction sliderAction); void scrollFavoritesTimelineTo(QAbstractSlider::SliderAction sliderAction); void scrollMainTimelineToWidget(QWidget *widget); void scrollDirectTimelineToWidget(QWidget *widget); void scrollActivityTimelineToWidget(QWidget *widget); void scrollFavoritesTimelineToWidget(QWidget *widget); void scrollToNewPosts(); void notifyTimelineUpdate(PumpController::requestTypes timelineType, int newPostCount, int highlightCount, int deletedPostCount, int filteredPostCount, int pendingPostCount); void setTimelineTabTitle(PumpController::requestTypes timelineType, int newPostCount, int highlightCount, int totalFeedPostCount); void setTitleAndTrayInfo(int currentTab); void setMinorFeedTitle(int newItemsCount, int newHighlightedItemsCount); void setMentionsFeedTitle(int newItemsCount, int newHighlightedItemsCount); void notifyMinorFeedUpdate(PumpController::requestTypes feedType, int newItemsCount, int highlightedCount, int filteredCount, int pendingItemsCount); void storeAvatar(QByteArray avatarData, QString avatarUrl); void storeImage(QByteArray imageData, QString imageUrl); void setStatusBarMessage(QString message); void setTransientStatusMessage(QString message); void markAllAsRead(); void startPost(QString title="", QString content=""); void refreshAllTimestamps(); void adjustTimelineSizes(); void showUserTimeline(QString userId, QString userName, QIcon userAvatar, QString userOutbox); void toggleSidePanel(bool shown); void toggleToolbar(bool shown); void toggleStatusBar(bool shown); void toggleFullscreen(bool enabled); void toggleMeanwhileFeed(); void toggleMentionsFeed(); void toggleActionsFeed(); void showFirstRunWizard(); void visitWebSite(); void visitBugTracker(); void visitPumpGuide(); void visitTips(); void visitUserList(); void visitPumpStatus(); void aboutDianara(); void toggleMainWindow(bool firstTime=false); void showAuthError(QString title, QString message); void onSessionManagerQuitRequest(QSessionManager &manager); void quitProgram(QString reason=""); protected: virtual void closeEvent(QCloseEvent *event); virtual void resizeEvent(QResizeEvent *event); private: ////////////////////////////////////// Menus QMenu *sessionMenu; QMenu *viewMenu; QMenu *settingsMenu; QMenu *helpMenu; QMenu *trayContextMenu; QAction *trayTitleSeparatorAction; QAction *trayShowWindowAction; QAction *sessionUpdateMainTimeline; QAction *sessionUpdateDirectTimeline; QAction *sessionUpdateActivityTimeline; QAction *sessionUpdateFavoritesTimeline; QAction *sessionUpdateMinorFeedMain; QAction *sessionUpdateMinorFeedDirect; QAction *sessionUpdateMinorFeedActivity; QAction *sessionAutoUpdates; QAction *sessionMarkAllAsRead; QAction *sessionPostNote; QAction *sessionQuit; QAction *viewSidePanel; QAction *viewToolbar; QAction *viewStatusBar; QAction *viewFullscreenAction; QAction *viewLogAction; QAction *settingsEditProfile; QAction *settingsAccount; QAction *settingsFilters; QAction *settingsConfigure; QAction *helpBasicHelp; QAction *helpShowWizard; QAction *helpVisitWebsite; QAction *helpVisitBugTracker; QAction *helpVisitPumpGuide; QAction *helpVisitPumpTips; QAction *helpVisitPumpUserList; QAction *helpVisitPumpStatus; QAction *helpAbout; ////////////////////////////////////// End menus // Info label for the menu QHBoxLayout *menuInfoLayout; QWidget *menuInfoWidget; QLabel *menuInfoLabel; // Toolbar QToolBar *mainToolBar; // Statusbar widgets QToolButton *statusStateButton; QToolButton *statusLogButton; QPushButton *statusAccountButton; // Shown when no account is configured bool statusAccountButtonUsed; QProgressBar *initializationProgressBar; QSplitter *mainSplitter; QWidget *leftSideWidget; QVBoxLayout *leftLayout; QHBoxLayout *leftTopLayout; QVBoxLayout *userInfoLayout; QToolBox *leftPanel; QAction *showMeanwhileFeed; QAction *showMentionsFeed; QAction *showActionsFeed; QWidget *rightSideWidget; QVBoxLayout *rightLayout; QTabWidget *tabWidget; int tabsPosition; bool tabsMovable; // User profile widgets QPushButton *avatarIconButton; QString avatarURL; QLabel *fullNameLabel; QLabel *userIdLabel; QLabel *userHometownLabel; QSystemTrayIcon *trayIcon; bool trayIconAvailable; int trayIconType; QPixmap trayCustomPixmap; int trayCurrentNewCount; int trayCurrentHLCount; GlobalObject *globalObject; AccountDialog *accountDialog; ProfileEditor *profileEditor; ConfigDialog *configDialog; FilterChecker *filterChecker; FilterEditor *filterEditor; LogViewer *logViewer; HelpWidget *helpWidget; PumpController *pumpController; FDNotifications *fdNotifier; MinorFeed *meanwhileFeed; MinorFeed *mentionsFeed; MinorFeed *actionsFeed; TimeLine *mainTimeline; QScrollArea *mainTimelineScrollArea; TimeLine *directTimeline; QScrollArea *directTimelineScrollArea; TimeLine *activityTimeline; QScrollArea *activityTimelineScrollArea; TimeLine *favoritesTimeline; QScrollArea *favoritesTimelineScrollArea; ContactManager *contactManager; Publisher *publisher; #ifdef QT_DBUS_LIB DBusInterface *dbusInterface; #endif bool firstRun; QString dataDirectory; // will have /images, /avatars, etc bool reallyQuitProgram; bool initializationComplete; int postIdsToStore; // Timer stuff int updateInterval; QTimer *updateTimer; QTimer *timestampsTimer; QTimer *delayedResizeTimer; QTimer *postInitTimer; QTimer *favoritesReloadTimer; QTimer *userDidSomethingTimer; QTimer *delayedScrollTimer; // Statusbar stuff QString oldStatusBarMessage; QString previousStatusMessageFromTimeline; QString previousStatusMessageFromMinorFeed; // user account-related data QString userID; }; #endif // MAINWINDOW_H dianara-v1.3.2/src/configdialog.h000644 000764 000764 00000012461 12573333661 016276 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef CONFIGDIALOG_H #define CONFIGDIALOG_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "globalobject.h" #include "fontpicker.h" #include "colorpicker.h" #include "notifications.h" #include "proxydialog.h" class ConfigDialog : public QWidget { Q_OBJECT public: ConfigDialog(GlobalObject *globalObject, QString dataDirectory, int updateInterval, int tabsPosition, bool tabsMovable, FDNotifications *notifier, QWidget *parent); ~ConfigDialog(); void syncNotifierOptions(); QString checkNotifications(int notificationStyle); void setPublicPosts(bool value); signals: void configurationChanged(); void filterEditorRequested(); public slots: void saveConfiguration(); void pickCustomIconFile(); void showDemoNotification(int notificationStyle); void toggleNotificationDetails(int currentOption); void toggleCustomIconButton(int currentOption); protected: virtual void closeEvent(QCloseEvent *event); virtual void hideEvent(QHideEvent *event); private: QVBoxLayout *mainLayout; QHBoxLayout *topLayout; QListWidget *categoriesListWidget; QStackedWidget *categoriesStackedWidget; // Page 1, general options QWidget *generalOptionsWidget; QFormLayout *generalOptionsLayout; QSpinBox *updateIntervalSpinbox; QComboBox *tabsPositionCombobox; QCheckBox *tabsMovableCheckbox; QPushButton *proxyConfigButton; ProxyDialog *proxyDialog; QPushButton *filterEditorButton; // Page 2, fonts QWidget *fontOptionsWidget; QVBoxLayout *fontOptionsLayout; FontPicker *fontPicker1; FontPicker *fontPicker2; FontPicker *fontPicker3; FontPicker *fontPicker4; // Page 3, colors QWidget *colorOptionsWidget; QVBoxLayout *colorOptionsLayout; ColorPicker *colorPicker1; ColorPicker *colorPicker2; ColorPicker *colorPicker3; ColorPicker *colorPicker4; ColorPicker *colorPicker5; ColorPicker *colorPicker6; // Page 4, timelines options QWidget *timelinesOptionsWidget; QFormLayout *timelinesOptionsLayout; QSpinBox *postsPerPageMainSpinbox; QSpinBox *postsPerPageOtherSpinbox; QComboBox *minorFeedSnippetsCombobox; QSpinBox *snippetLimitSpinbox; QCheckBox *showDeletedCheckbox; QCheckBox *hideDuplicatesCheckbox; QCheckBox *jumpToNewCheckbox; // Page 5, posts options QWidget *postsOptionsWidget; QFormLayout *postsOptionsLayout; QComboBox *postAvatarSizeCombobox; QCheckBox *showExtendedSharesCheckbox; QCheckBox *showExtraInfoCheckbox; QCheckBox *postHLAuthorCommentsCheckbox; QCheckBox *postHLOwnCommentsCheckbox; QCheckBox *postIgnoreSslInImages; // Page 6, composer options QWidget *composerOptionsWidget; QFormLayout *composerOptionsLayout; QCheckBox *publicPostsCheckbox; QCheckBox *useFilenameAsTitleCheckbox; QCheckBox *showCharacterCounterCheckbox; // Page 7, privacy options QWidget *privacyOptionsWidget; QFormLayout *privacyOptionsLayout; QCheckBox *silentFollowsCheckbox; QCheckBox *silentListsCheckbox; QCheckBox *silentLikesCheckbox; // Page 8, notifications options QWidget *notificationOptionsWidget; QFormLayout *notificationOptionsLayout; QComboBox *notificationStyleCombobox; QLabel *notificationsStatusLabel; QCheckBox *notifyNewTLCheckbox; QCheckBox *notifyHLTLCheckbox; QCheckBox *notifyNewMWCheckbox; QCheckBox *notifyHLMWCheckbox; // Page 9, system tray options QWidget *systrayOptionsWidget; QFormLayout *systrayOptionsLayout; QComboBox *systrayIconTypeCombobox; QPushButton *systrayCustomIconButton; QString systrayCustomIconFN; QString systrayIconLastUsedDir; QCheckBox *systrayHideCheckbox; // Widgets below the tab widget QLabel *dataDirectoryLabel; QHBoxLayout *buttonsLayout; QPushButton *saveConfigButton; QPushButton *cancelButton; QAction *closeAction; FDNotifications *fdNotifier; GlobalObject *globalObj; }; #endif // CONFIGDIALOG_H dianara-v1.3.2/src/configdialog.cpp000644 000764 000764 00000115325 12612152135 016621 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "configdialog.h" ConfigDialog::ConfigDialog(GlobalObject *globalObject, QString dataDirectory, int updateInterval, int tabsPosition, bool tabsMovable, FDNotifications *notifier, QWidget *parent) : QWidget(parent) { this->globalObj = globalObject; this->fdNotifier = notifier; this->setWindowTitle(tr("Program Configuration") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("configure", QIcon(":/images/button-configure.png"))); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::ApplicationModal); this->setMinimumSize(640, 520); QSettings settings; QSize savedWindowsize = settings.value("ConfigDialog/configWindowSize").toSize(); if (savedWindowsize.isValid()) { this->resize(savedWindowsize); } settings.beginGroup("Configuration"); // Standalone Proxy config window QByteArray proxyPasswd = QByteArray::fromBase64(settings.value("proxyPassword") .toByteArray()); proxyDialog = new ProxyDialog(settings.value("proxyType", 0).toInt(), settings.value("proxyHostname").toString(), settings.value("proxyPort").toString(), settings.value("proxyUseAuth", false).toBool(), settings.value("proxyUser").toString(), QString::fromLocal8Bit(proxyPasswd), this); //////////////////////////////////////////////////////////////// Upper part // Page 1, general options this->generalOptionsLayout = new QFormLayout(); updateIntervalSpinbox = new QSpinBox(this); updateIntervalSpinbox->setRange(2, 99); // 2-99 min updateIntervalSpinbox->setSuffix(" "+ tr("minutes")); updateIntervalSpinbox->setValue(updateInterval); generalOptionsLayout->addRow(tr("Timeline &update interval"), updateIntervalSpinbox); // ------------------------------------------------ QFrame *lineFrame1 = new QFrame(this); lineFrame1->setFrameStyle(QFrame::HLine); generalOptionsLayout->addRow(lineFrame1); tabsPositionCombobox = new QComboBox(this); tabsPositionCombobox->addItem(QIcon::fromTheme("arrow-up"), tr("Top")); tabsPositionCombobox->addItem(QIcon::fromTheme("arrow-down"), tr("Bottom")); tabsPositionCombobox->addItem(QIcon::fromTheme("arrow-left"), tr("Left side", "tabs on left side/west; RTL not affected")); tabsPositionCombobox->addItem(QIcon::fromTheme("arrow-right"), tr("Right side", "tabs on right side/east; RTL not affected")); tabsPositionCombobox->setCurrentIndex(tabsPosition); generalOptionsLayout->addRow(tr("&Tabs position"), tabsPositionCombobox); tabsMovableCheckbox = new QCheckBox(this); tabsMovableCheckbox->setChecked(tabsMovable); generalOptionsLayout->addRow(tr("&Movable tabs"), tabsMovableCheckbox); // ------------------------------------------------ QFrame *lineFrame2 = new QFrame(this); lineFrame2->setFrameStyle(QFrame::HLine); generalOptionsLayout->addRow(lineFrame2); proxyConfigButton = new QPushButton(QIcon::fromTheme("preferences-system-network", QIcon(":/images/button-configure.png")), tr("Pro&xy Settings"), this); connect(proxyConfigButton, SIGNAL(clicked()), proxyDialog, SLOT(show())); generalOptionsLayout->addRow(tr("Network configuration"), proxyConfigButton); filterEditorButton = new QPushButton(QIcon::fromTheme("view-filter", QIcon(":/images/button-filter.png")), tr("Set Up F&ilters"), this); connect(filterEditorButton, SIGNAL(clicked()), this, SIGNAL(filterEditorRequested())); generalOptionsLayout->addRow(tr("Filtering rules"), filterEditorButton); generalOptionsWidget = new QWidget(this); generalOptionsWidget->setLayout(generalOptionsLayout); // Page 2, fonts this->fontOptionsLayout = new QVBoxLayout(); fontPicker1 = new FontPicker(tr("Post Titles"), globalObj->getPostTitleFont(), this); fontPicker2 = new FontPicker(tr("Post Contents"), globalObj->getPostContentsFont(), this); fontPicker3 = new FontPicker(tr("Comments"), globalObj->getCommentsFont(), this); fontPicker4 = new FontPicker(tr("Minor Feeds"), globalObj->getMinorFeedFont(), this); // FIXME: more for "Timestamps" or something else? // FIXME: add a "base font size" option fontOptionsLayout->addWidget(fontPicker1); fontOptionsLayout->addWidget(fontPicker2); fontOptionsLayout->addWidget(fontPicker3); fontOptionsLayout->addWidget(fontPicker4); fontOptionsLayout->addStretch(1); fontOptionsWidget = new QWidget(this); fontOptionsWidget->setLayout(fontOptionsLayout); // Page 3, colors this->colorOptionsLayout = new QVBoxLayout(); QStringList colorList = globalObj->getColorsList(); colorPicker1 = new ColorPicker(tr("You are among the recipients " "of the activity, such as " "a comment addressed to you.") + "\n" + tr("Used also when highlighting posts " "addressed to you in the timelines."), colorList.at(0), this); colorOptionsLayout->addWidget(colorPicker1); colorPicker2 = new ColorPicker(tr("The activity is in reply to something " "done by you, such as a comment posted " "in reply to one of your notes."), colorList.at(1), this); colorOptionsLayout->addWidget(colorPicker2); colorPicker3 = new ColorPicker(tr("You are the object of the activity, " "such as someone adding you to a list."), colorList.at(2), this); colorOptionsLayout->addWidget(colorPicker3); colorPicker4 = new ColorPicker(tr("The activity is related to one of " "your objects, such as someone " "liking one of your posts.") + "\n" + tr("Used also when highlighting your " "own posts in the timelines."), colorList.at(3), this); colorOptionsLayout->addWidget(colorPicker4); colorPicker5 = new ColorPicker(tr("Item highlighted due to filtering rules."), colorList.at(4), this); colorOptionsLayout->addWidget(colorPicker5); colorPicker6 = new ColorPicker(tr("Item is new."), colorList.at(5), this); colorOptionsLayout->addWidget(colorPicker6); colorOptionsWidget = new QWidget(this); colorOptionsWidget->setLayout(colorOptionsLayout); // Page 4, timelines options timelinesOptionsLayout = new QFormLayout(); postsPerPageMainSpinbox = new QSpinBox(this); postsPerPageMainSpinbox->setRange(5, 50); // 5-50 ppp postsPerPageMainSpinbox->setSuffix(" "+ tr("posts", "Goes after a number, as: " "25 posts")); postsPerPageMainSpinbox->setValue(globalObj->getPostsPerPageMain()); timelinesOptionsLayout->addRow(tr("&Posts per page, main timeline"), postsPerPageMainSpinbox); postsPerPageOtherSpinbox = new QSpinBox(this); postsPerPageOtherSpinbox->setRange(1, 30); // 1-30 ppp postsPerPageOtherSpinbox->setSuffix(" "+ tr("posts", "This goes after a number, " "like: 10 posts")); postsPerPageOtherSpinbox->setValue(globalObj->getPostsPerPageOther()); timelinesOptionsLayout->addRow(tr("Posts per page, &other timelines"), postsPerPageOtherSpinbox); minorFeedSnippetsCombobox = new QComboBox(this); minorFeedSnippetsCombobox->addItem(tr("Highlighted activities, except mine")); minorFeedSnippetsCombobox->addItem(tr("Any highlighted activity")); minorFeedSnippetsCombobox->addItem(tr("Always")); minorFeedSnippetsCombobox->addItem(tr("Never")); minorFeedSnippetsCombobox->setCurrentIndex(globalObj->getMinorFeedSnippetsType()); timelinesOptionsLayout->addRow(tr("Show snippets in minor feeds"), minorFeedSnippetsCombobox); snippetLimitSpinbox = new QSpinBox(this); snippetLimitSpinbox->setRange(10, 10000); snippetLimitSpinbox->setSuffix(" " + tr("characters", "This is a suffix, after a number")); snippetLimitSpinbox->setValue(globalObj->getSnippetsCharLimit()); timelinesOptionsLayout->addRow(tr("Snippet limit"), snippetLimitSpinbox); showDeletedCheckbox = new QCheckBox(this); showDeletedCheckbox->setChecked(globalObj->getShowDeleted()); timelinesOptionsLayout->addRow(tr("Show information for deleted posts"), // FIXME? showDeletedCheckbox); hideDuplicatesCheckbox = new QCheckBox(this); hideDuplicatesCheckbox->setChecked(globalObj->getHideDuplicates()); timelinesOptionsLayout->addRow(tr("Hide duplicated posts"), hideDuplicatesCheckbox); jumpToNewCheckbox = new QCheckBox(this); jumpToNewCheckbox->setChecked(globalObj->getJumpToNewOnUpdate()); timelinesOptionsLayout->addRow(tr("Jump to new posts line on update"), jumpToNewCheckbox); timelinesOptionsWidget = new QWidget(this); timelinesOptionsWidget->setLayout(timelinesOptionsLayout); // Page 5, posts options postAvatarSizeCombobox = new QComboBox(this); postAvatarSizeCombobox->addItem("32x32"); postAvatarSizeCombobox->addItem("48x48"); postAvatarSizeCombobox->addItem("64x64"); postAvatarSizeCombobox->addItem("96x96"); postAvatarSizeCombobox->addItem("128x128"); postAvatarSizeCombobox->addItem("256x256"); postAvatarSizeCombobox->setCurrentIndex(globalObj->getPostAvatarSizeIndex()); showExtendedSharesCheckbox = new QCheckBox(this); showExtendedSharesCheckbox->setChecked(globalObj->getPostExtendedShares()); showExtraInfoCheckbox = new QCheckBox(this); showExtraInfoCheckbox->setChecked(globalObj->getPostShowExtraInfo()); postHLAuthorCommentsCheckbox = new QCheckBox(this); postHLAuthorCommentsCheckbox->setChecked(globalObj->getPostHLAuthorComments()); postHLOwnCommentsCheckbox = new QCheckBox(this); postHLOwnCommentsCheckbox->setChecked(globalObj->getPostHLOwnComments()); postIgnoreSslInImages = new QCheckBox(tr("Only for images inserted from " "web sites.") + "\n" + tr("Use with care."), this); postIgnoreSslInImages->setChecked(globalObj->getPostIgnoreSslInImages()); postsOptionsLayout = new QFormLayout(); postsOptionsLayout->addRow(tr("Avatar size"), postAvatarSizeCombobox); postsOptionsLayout->addRow(tr("Show extended share information"), showExtendedSharesCheckbox); postsOptionsLayout->addRow(tr("Show extra information"), showExtraInfoCheckbox); postsOptionsLayout->addRow(tr("Highlight post author's comments"), postHLAuthorCommentsCheckbox); postsOptionsLayout->addRow(tr("Highlight your own comments"), postHLOwnCommentsCheckbox); postsOptionsLayout->addRow(tr("Ignore SSL errors in images"), postIgnoreSslInImages); postsOptionsWidget = new QWidget(this); postsOptionsWidget->setLayout(postsOptionsLayout); // Page 6, composer options publicPostsCheckbox = new QCheckBox(this); publicPostsCheckbox->setChecked(globalObj->getPublicPostsByDefault()); useFilenameAsTitleCheckbox = new QCheckBox(this); useFilenameAsTitleCheckbox->setChecked(globalObj->getUseFilenameAsTitle()); showCharacterCounterCheckbox = new QCheckBox(this); showCharacterCounterCheckbox->setChecked(globalObj->getShowCharacterCounter()); composerOptionsLayout = new QFormLayout(); composerOptionsLayout->addRow(tr("Public posts as &default"), publicPostsCheckbox); composerOptionsLayout->addRow(tr("Use attachment filename as initial post title"), useFilenameAsTitleCheckbox); composerOptionsLayout->addRow(tr("Show character counter"), showCharacterCounterCheckbox); composerOptionsWidget = new QWidget(this); composerOptionsWidget->setLayout(composerOptionsLayout); // Page 7, privacy options silentFollowsCheckbox = new QCheckBox(this); silentFollowsCheckbox->setChecked(globalObj->getSilentFollows()); silentListsCheckbox = new QCheckBox(this); silentListsCheckbox->setChecked(globalObj->getSilentLists()); silentLikesCheckbox = new QCheckBox(this); silentLikesCheckbox->setChecked(globalObj->getSilentLikes()); privacyOptionsLayout = new QFormLayout(); privacyOptionsLayout->addRow(tr("Don't inform followers when following someone"), silentFollowsCheckbox); privacyOptionsLayout->addRow(tr("Don't inform followers when handling lists"), silentListsCheckbox); privacyOptionsLayout->addRow(tr("Inform only the author when liking things"), silentLikesCheckbox); privacyOptionsWidget = new QWidget(this); privacyOptionsWidget->setLayout(privacyOptionsLayout); // Page 8, notifications options notificationOptionsLayout = new QFormLayout(); notificationStyleCombobox = new QComboBox(this); notificationStyleCombobox->addItem(QIcon::fromTheme("preferences-desktop-notification"), tr("As system notifications")); notificationStyleCombobox->addItem(QIcon::fromTheme("view-conversation-balloon"), tr("Using own notifications")); notificationStyleCombobox->addItem(QIcon::fromTheme("user-busy"), // dialog-cancel tr("Don't show notifications")); notificationStyleCombobox->setCurrentIndex(settings.value("showNotifications", 0).toInt()); notificationOptionsLayout->addRow(tr("Notification Style"), notificationStyleCombobox); connect(notificationStyleCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(toggleNotificationDetails(int))); notificationsStatusLabel = new QLabel(this); notificationOptionsLayout->addRow("", // Empty label on left to align with right column notificationsStatusLabel); notificationOptionsLayout->addRow(new QLabel("\n" + tr("Notify when receiving:"), this)); notifyNewTLCheckbox = new QCheckBox(this); notifyNewTLCheckbox->setChecked(settings.value("notifyNewTL", false).toBool()); notificationOptionsLayout->addRow(tr("New posts"), notifyNewTLCheckbox); notifyHLTLCheckbox = new QCheckBox(this); notifyHLTLCheckbox->setChecked(settings.value("notifyHLTL", true).toBool()); notificationOptionsLayout->addRow(tr("Highlighted posts"), notifyHLTLCheckbox); notifyNewMWCheckbox = new QCheckBox(this); notifyNewMWCheckbox->setChecked(settings.value("notifyNewMW", false).toBool()); notificationOptionsLayout->addRow(tr("New activities in minor feed"), notifyNewMWCheckbox); notifyHLMWCheckbox = new QCheckBox(this); notifyHLMWCheckbox->setChecked(settings.value("notifyHLMW", true).toBool()); notificationOptionsLayout->addRow(tr("Highlighted activities in minor feed"), notifyHLMWCheckbox); this->syncNotifierOptions(); // Initial check to see if currently selected style is available this->checkNotifications(notificationStyleCombobox->currentIndex()); notificationOptionsWidget = new QWidget(this); notificationOptionsWidget->setLayout(notificationOptionsLayout); // Check FD.org notifications availability when selecting an option connect(notificationStyleCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(showDemoNotification(int))); // Page 9, systray options systrayOptionsLayout = new QFormLayout(); systrayIconTypeCombobox = new QComboBox(this); systrayIconTypeCombobox->addItem(tr("Default")); systrayIconTypeCombobox->addItem(tr("System iconset, if available")); systrayIconTypeCombobox->addItem(tr("Show your current avatar")); systrayIconTypeCombobox->addItem(tr("Custom icon")); systrayIconTypeCombobox->setCurrentIndex(settings.value("systrayIconType", 0).toInt()); systrayOptionsLayout->addRow(tr("System Tray Icon &Type"), systrayIconTypeCombobox); connect(systrayIconTypeCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(toggleCustomIconButton(int))); systrayCustomIconButton = new QPushButton(tr("S&elect..."), this); systrayIconLastUsedDir = QDir::homePath(); systrayCustomIconFN = settings.value("systrayCustomIconFN").toString(); // FIXME: merge this with code used in pickCustomIconFile() // and turn the warning messageBox into a label if (!QPixmap(systrayCustomIconFN).isNull()) { systrayCustomIconButton->setIcon(QIcon(systrayCustomIconFN)); systrayCustomIconButton->setToolTip("" + systrayCustomIconFN); } else { systrayCustomIconButton->setIcon(QIcon(":/icon/32x32/dianara.png")); } connect(systrayCustomIconButton, SIGNAL(clicked()), this, SLOT(pickCustomIconFile())); systrayOptionsLayout->addRow(tr("Custom &Icon"), systrayCustomIconButton); // Enable/disable initially based on loaded config this->toggleCustomIconButton(systrayIconTypeCombobox->currentIndex()); systrayHideCheckbox = new QCheckBox(this); systrayHideCheckbox->setChecked(globalObject->getHideInTray()); systrayOptionsLayout->addRow(tr("Hide window on startup"), systrayHideCheckbox); systrayOptionsWidget = new QWidget(this); systrayOptionsWidget->setLayout(systrayOptionsLayout); settings.endGroup(); /////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////// List of categories and stacked widget categoriesListWidget = new QListWidget(this); categoriesListWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); categoriesListWidget->setMinimumWidth(140); // kinda TMP/FIXME #if 0 // enable for large-icon-mode with text below categoriesListWidget->setViewMode(QListView::IconMode); categoriesListWidget->setFlow(QListView::TopToBottom); categoriesListWidget->setIconSize(QSize(48, 48)); categoriesListWidget->setWrapping(false); categoriesListWidget->setMovement(QListView::Static); #endif categoriesListWidget->setIconSize(QSize(32, 32)); categoriesListWidget->setUniformItemSizes(true); categoriesListWidget->setWordWrap(true); categoriesListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); categoriesListWidget->addItem(tr("General Options")); categoriesListWidget->item(0)->setIcon(QIcon::fromTheme("preferences-other", QIcon(":/images/button-configure.png"))); categoriesListWidget->addItem(tr("Fonts")); categoriesListWidget->item(1)->setIcon(QIcon::fromTheme("preferences-desktop-font", QIcon(":/images/button-configure.png"))); categoriesListWidget->addItem(tr("Colors")); categoriesListWidget->item(2)->setIcon(QIcon::fromTheme("preferences-desktop-color", QIcon(":/images/button-configure.png"))); categoriesListWidget->addItem(tr("Timelines")); categoriesListWidget->item(3)->setIcon(QIcon::fromTheme("view-list-details", QIcon(":/images/feed-inbox.png"))); categoriesListWidget->addItem(tr("Posts")); categoriesListWidget->item(4)->setIcon(QIcon::fromTheme("mail-message", QIcon(":/images/button-post.png"))); categoriesListWidget->addItem(tr("Composer")); categoriesListWidget->item(5)->setIcon(QIcon::fromTheme("document-edit", QIcon(":/images/button-edit.png"))); categoriesListWidget->addItem(tr("Privacy")); categoriesListWidget->item(6)->setIcon(QIcon::fromTheme("object-locked", QIcon(":/images/button-password.png"))); categoriesListWidget->addItem(tr("Notifications")); categoriesListWidget->item(7)->setIcon(QIcon::fromTheme("preferences-desktop-notification", QIcon(":/images/button-online.png"))); categoriesListWidget->addItem(tr("System Tray")); // dashboard-show ? categoriesListWidget->item(8)->setIcon(QIcon::fromTheme("configure-toolbars", QIcon(":/images/button-configure.png"))); categoriesStackedWidget = new QStackedWidget(this); categoriesStackedWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); categoriesStackedWidget->addWidget(generalOptionsWidget); categoriesStackedWidget->addWidget(fontOptionsWidget); categoriesStackedWidget->addWidget(colorOptionsWidget); categoriesStackedWidget->addWidget(timelinesOptionsWidget); categoriesStackedWidget->addWidget(postsOptionsWidget); categoriesStackedWidget->addWidget(composerOptionsWidget); categoriesStackedWidget->addWidget(privacyOptionsWidget); categoriesStackedWidget->addWidget(notificationOptionsWidget); categoriesStackedWidget->addWidget(systrayOptionsWidget); connect(categoriesListWidget, SIGNAL(currentRowChanged(int)), categoriesStackedWidget, SLOT(setCurrentIndex(int))); topLayout = new QHBoxLayout(); topLayout->addWidget(categoriesListWidget); topLayout->addWidget(categoriesStackedWidget); /////////////////////////////////////////////////////////////// Bottom part // Label to show where the data directory is dataDirectoryLabel = new QLabel(tr("Dianara stores data in this folder:") + QString(" %2") .arg(dataDirectory).arg(dataDirectory), this); dataDirectoryLabel->setWordWrap(true); dataDirectoryLabel->setOpenExternalLinks(true); // Save / Cancel buttons saveConfigButton = new QPushButton(QIcon::fromTheme("document-save", QIcon(":/images/button-save.png")), tr("&Save Configuration"), this); connect(saveConfigButton, SIGNAL(clicked()), this, SLOT(saveConfiguration())); cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("&Cancel"), this); connect(cancelButton, SIGNAL(clicked()), this, SLOT(hide())); this->buttonsLayout = new QHBoxLayout(); buttonsLayout->setAlignment(Qt::AlignRight); buttonsLayout->addWidget(saveConfigButton); buttonsLayout->addWidget(cancelButton); // ESC to close closeAction = new QAction(this); closeAction->setShortcut(QKeySequence(Qt::Key_Escape)); connect(closeAction, SIGNAL(triggered()), this, SLOT(hide())); this->addAction(closeAction); //// Set up main layout mainLayout = new QVBoxLayout(); mainLayout->addLayout(topLayout, 10); mainLayout->addSpacing(8); mainLayout->addStretch(1); mainLayout->addWidget(dataDirectoryLabel); mainLayout->addSpacing(8); mainLayout->addStretch(2); mainLayout->addLayout(buttonsLayout); this->setLayout(mainLayout); // Activate the first category (so the row already looks selected) this->categoriesListWidget->setCurrentRow(0); this->categoriesListWidget->setFocus(); qDebug() << "Config dialog created"; } ConfigDialog::~ConfigDialog() { qDebug() << "Config dialog destroyed"; } void ConfigDialog::syncNotifierOptions() { this->toggleNotificationDetails(notificationStyleCombobox->currentIndex()); this->fdNotifier->setNotificationOptions(notificationStyleCombobox->currentIndex(), notifyNewTLCheckbox->isChecked(), notifyHLTLCheckbox->isChecked(), notifyNewMWCheckbox->isChecked(), notifyHLMWCheckbox->isChecked()); } /* * Get a demo message for the current notification style * * Show also a warning is system notifications are not available * */ QString ConfigDialog::checkNotifications(int notificationStyle) { QString demoText; if (notificationStyle == FDNotifications::SystemNotifications) { if (fdNotifier->areNotificationsAvailable()) { demoText = tr("This is a system notification"); } else { demoText = tr("System notifications are not available!") + "
" + tr("Own notifications will be used."); notificationsStatusLabel->setText("" + demoText + ""); /* FIXME: Should also check availability of system tray icon, * needed to show Qt's balloon notifications */ } } else { demoText = tr("This is a basic notification"); } return demoText; } void ConfigDialog::setPublicPosts(bool value) { this->publicPostsCheckbox->setChecked(value); this->saveConfiguration(); // This might be overkill -- FIXME TMP } ////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// SLOTS ////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void ConfigDialog::saveConfiguration() { QSettings settings; if (!settings.isWritable()) { // TMP FIXME: notify user properly qDebug() << "ERROR SAVING CONFIGURATION (maybe disk full?)"; return; } settings.beginGroup("Configuration"); // General settings.setValue("updateInterval", this->updateIntervalSpinbox->value()); settings.setValue("tabsPosition", this->tabsPositionCombobox->currentIndex()); settings.setValue("tabsMovable", this->tabsMovableCheckbox->isChecked()); // Fonts settings.setValue("font1", this->fontPicker1->getFontInfo()); settings.setValue("font2", this->fontPicker2->getFontInfo()); settings.setValue("font3", this->fontPicker3->getFontInfo()); settings.setValue("font4", this->fontPicker4->getFontInfo()); this->globalObj->syncFontSettings(this->fontPicker1->getFontInfo(), this->fontPicker2->getFontInfo(), this->fontPicker3->getFontInfo(), this->fontPicker4->getFontInfo()); // Colors settings.setValue("color1", this->colorPicker1->getCurrentColor()); settings.setValue("color2", this->colorPicker2->getCurrentColor()); settings.setValue("color3", this->colorPicker3->getCurrentColor()); settings.setValue("color4", this->colorPicker4->getCurrentColor()); settings.setValue("color5", this->colorPicker5->getCurrentColor()); settings.setValue("color6", this->colorPicker6->getCurrentColor()); QStringList highlightColorsList; highlightColorsList << this->colorPicker1->getCurrentColor() << this->colorPicker2->getCurrentColor() << this->colorPicker3->getCurrentColor() << this->colorPicker4->getCurrentColor() << this->colorPicker5->getCurrentColor() << this->colorPicker6->getCurrentColor(); this->globalObj->syncColorSettings(highlightColorsList); // Timelines settings.setValue("postsPerPageMain", this->postsPerPageMainSpinbox->value()); settings.setValue("postsPerPageOther", this->postsPerPageOtherSpinbox->value()); settings.setValue("minorFeedSnippets", this->minorFeedSnippetsCombobox->currentIndex()); settings.setValue("snippetsCharLimit", this->snippetLimitSpinbox->value()); settings.setValue("showDeletedPosts", this->showDeletedCheckbox->isChecked()); settings.setValue("hideDuplicatedPosts", this->hideDuplicatesCheckbox->isChecked()); settings.setValue("jumpToNewOnUpdate", this->jumpToNewCheckbox->isChecked()); this->globalObj->syncTimelinesSettings(this->postsPerPageMainSpinbox->value(), this->postsPerPageOtherSpinbox->value(), this->minorFeedSnippetsCombobox->currentIndex(), this->snippetLimitSpinbox->value(), this->showDeletedCheckbox->isChecked(), this->hideDuplicatesCheckbox->isChecked(), this->jumpToNewCheckbox->isChecked()); // Posts settings.setValue("postAvatarSizeIndex", this->postAvatarSizeCombobox->currentIndex()); settings.setValue("postExtendedShares", this->showExtendedSharesCheckbox->isChecked()); settings.setValue("postShowExtraInfo", this->showExtraInfoCheckbox->isChecked()); settings.setValue("postHLAuthorComments", this->postHLAuthorCommentsCheckbox->isChecked()); settings.setValue("postHLOwnComments", this->postHLOwnCommentsCheckbox->isChecked()); settings.setValue("postIgnoreSslInImages", this->postIgnoreSslInImages->isChecked()); this->globalObj->syncPostSettings(this->postAvatarSizeCombobox->currentIndex(), this->showExtendedSharesCheckbox->isChecked(), this->showExtraInfoCheckbox->isChecked(), this->postHLAuthorCommentsCheckbox->isChecked(), this->postHLOwnCommentsCheckbox->isChecked(), this->postIgnoreSslInImages->isChecked()); // Composer settings.setValue("publicPosts", this->publicPostsCheckbox->isChecked()); settings.setValue("useFilenameAsTitle", this->useFilenameAsTitleCheckbox->isChecked()); settings.setValue("showCharacterCounter", this->showCharacterCounterCheckbox->isChecked()); this->globalObj->syncComposerSettings(this->publicPostsCheckbox->isChecked(), this->useFilenameAsTitleCheckbox->isChecked(), this->showCharacterCounterCheckbox->isChecked()); // Privacy settings.setValue("silentFollows", this->silentFollowsCheckbox->isChecked()); settings.setValue("silentLists", this->silentListsCheckbox->isChecked()); settings.setValue("silentLikes", this->silentLikesCheckbox->isChecked()); this->globalObj->syncPrivacySettings(this->silentFollowsCheckbox->isChecked(), this->silentListsCheckbox->isChecked(), this->silentLikesCheckbox->isChecked()); // Notifications settings.setValue("showNotifications", this->notificationStyleCombobox->currentIndex()); settings.setValue("notifyNewTL", this->notifyNewTLCheckbox->isChecked()); settings.setValue("notifyHLTL", this->notifyHLTLCheckbox->isChecked()); settings.setValue("notifyNewMW", this->notifyNewMWCheckbox->isChecked()); settings.setValue("notifyHLMW", this->notifyHLMWCheckbox->isChecked()); this->syncNotifierOptions(); this->globalObj->syncNotificationSettings(); // FIXME, still empty // Tray settings.setValue("systrayIconType", this->systrayIconTypeCombobox->currentIndex()); settings.setValue("systrayCustomIconFN", this->systrayCustomIconFN); settings.setValue("systrayHideInTray", this->systrayHideCheckbox->isChecked()); this->globalObj->syncTrayOptions(this->systrayHideCheckbox->isChecked()); // FIXME, some still empty settings.endGroup(); settings.sync(); emit configurationChanged(); // Ask MainWindow to reload some stuff this->hide(); // this->close() would end the program if mainWindow was hidden qDebug() << "ConfigDialog: config saved"; } void ConfigDialog::pickCustomIconFile() { systrayCustomIconFN = QFileDialog::getOpenFileName(this, tr("Select custom icon"), systrayIconLastUsedDir, tr("Image files") + " (*.png *.jpg *.jpeg *.gif);;" + tr("All files") + " (*)"); if (!systrayCustomIconFN.isEmpty()) { qDebug() << "Selected" << systrayCustomIconFN << "as new custom tray icon"; QFileInfo fileInfo(systrayCustomIconFN); this->systrayIconLastUsedDir = fileInfo.path(); QPixmap iconPixmap = QPixmap(systrayCustomIconFN); if (!iconPixmap.isNull()) { this->systrayCustomIconButton->setIcon(QIcon(systrayCustomIconFN)); this->systrayCustomIconButton->setToolTip("" + systrayCustomIconFN); } else { QMessageBox::warning(this, tr("Invalid image"), tr("The selected image is not valid.")); qDebug() << "Invalid tray icon file selected"; } } } void ConfigDialog::showDemoNotification(int notificationStyle) { notificationsStatusLabel->clear(); if (notificationStyle == FDNotifications::NoNotifications) { return; } this->syncNotifierOptions(); QString demoNotificationText = this->checkNotifications(notificationStyle); this->fdNotifier->showMessage(demoNotificationText); } void ConfigDialog::toggleNotificationDetails(int currentOption) { bool state = true; if (currentOption == 2) // No notifications; disable details so it's clearer { state = false; } this->notifyNewTLCheckbox->setEnabled(state); this->notifyHLTLCheckbox->setEnabled(state); this->notifyNewMWCheckbox->setEnabled(state); this->notifyHLMWCheckbox->setEnabled(state); } void ConfigDialog::toggleCustomIconButton(int currentOption) { bool state = false; if (currentOption == 3) // Enable only for last option, "Custom icon" { state = true; } this->systrayCustomIconButton->setEnabled(state); } ////////////////////////////////////////////////////////////////////////////// /////////////////////////////// PROTECTED //////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void ConfigDialog::closeEvent(QCloseEvent *event) { this->hide(); event->ignore(); } void ConfigDialog::hideEvent(QHideEvent *event) { QSettings settings; settings.setValue("ConfigDialog/configWindowSize", this->size()); event->accept(); } dianara-v1.3.2/src/pumpcontroller.h000644 000764 000764 00000031404 12573333661 016734 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef PUMPCONTROLLER_H #define PUMPCONTROLLER_H #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #endif #include #include #include #include // TMP #include #include #include #include #include #include /// For JSON parsing #include #include #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // Qt 4 #include #include #else // Qt 5 #include #endif // For OAuth authentication #include #include "mischelpers.h" #include "asperson.h" #include "asobject.h" class PumpController : public QObject { Q_OBJECT public: enum requestTypes { NoRequest, ClientRegistrationRequest, TokenRequest, UserProfileRequest, UpdateProfileRequest, UpdateEmailRequest, FollowingListRequest, FollowersListRequest, ListsListRequest, SiteUserListRequest, CreatePersonListRequest, DeletePersonListRequest, PersonListRequest, AddMemberToListRequest, RemoveMemberFromListRequest, CreateGroupRequest, DeleteGroupRequest, JoinGroupRequest, LeaveGroupRequest, MainTimelineRequest, DirectTimelineRequest, ActivityTimelineRequest, FavoritesTimelineRequest, UserTimelineRequest, PostLikesRequest, PostCommentsRequest, PostSharesRequest, MinorFeedMainRequest, MinorFeedDirectRequest, MinorFeedActivityRequest, PublishPostRequest, LikePostRequest, CommentPostRequest, SharePostRequest, UnsharePostRequest, DeletePostRequest, UpdatePostRequest, UpdateCommentRequest, FollowContactRequest, UnfollowContactRequest, AvatarRequest, ImageRequest, MediaRequest, UploadFileRequest, UploadMediaForPostRequest, UploadAvatarRequest, PublishAvatarRequest }; explicit PumpController(QObject *parent = 0); ~PumpController(); void setProxyConfig(QNetworkProxy::ProxyType proxyType, QString hostname, int port, bool useAuth, QString user, QString password); bool needsProxyPassword(); void setProxyPassword(QString password); void updateApiUrls(); void setPostsPerPageMain(int ppp); void setPostsPerPageOther(int ppp); void setNewUserId(QString userId); void setUserCredentials(QString userId); QString currentUserId(); QString currentUsername(); QString currentServerScheme(); QString currentServerUrl(); QString currentFollowersUrl(); int currentFollowersCount(); int currentFollowingCount(); bool currentlyAuthorized(); void getUserProfile(QString userId); void updateUserProfile(QString avatarUrl, QString fullName, QString hometown, QString bio); void updateUserEmail(QString newEmail, QString password); void enqueueAvatarForDownload(QString url); void enqueueImageForDownload(QString url); void getAvatar(QString avatarUrl); void getImage(QString imageUrl); QNetworkReply *getMedia(QString mediaUrl); void notifyAvatarStored(QString avatarUrl, QString avatarFilename); void notifyImageStored(QString imageUrl); void notifyImageFailed(QString imageUrl); void getContactList(QString listType, int offset=0); void getSiteUserList(); bool userInFollowing(QString contactId); void updateInternalFollowingIdList(QStringList idList); void removeFromInternalFollowingList(QString id); void getListsList(); void createPersonList(QString name, QString description); void deletePersonList(QString id); void getPersonList(QString url); void addPersonToList(QString listId, QString personId); void removePersonFromList(QString listId, QString personId); void createGroup(QString name, QString summary, QString description); void joinGroup(QString id); void leaveGroup(QString id); void getPostLikes(QString postLikesUrl); void getPostComments(QString postCommentsUrl); void getPostShares(QString postSharesUrl); void getFeed(requestTypes feedType, int itemCount, QString url = "", int feedOffset = 0); static QStringList getFeedNameAndPath(int feedType); QString getFeedApiUrl(int feedType); QNetworkRequest prepareRequest(QString url, QOAuth::HttpMethod method, requestTypes requestType, QOAuth::ParamMap paramMap = QOAuth::ParamMap(), QString contentTypeString="application/json"); QByteArray prepareJSON(QVariantMap jsonVariantMap); QVariantMap parseJSON(QByteArray rawData, bool *parsedOk); QNetworkReply *uploadFile(QString filename, QString contentType, requestTypes uploadType = UploadFileRequest); QList processAudience(QMap audienceMap); bool urlIsInOurHost(QString url); void addCommentUrlToSeenList(QString id, QString url); QString commentsUrlForPost(QString id); void showTransientMessage(QString message); void showStatusMessageAndLogIt(QString message, QString url=""); void showObjectSnippetAndLogIt(QString message, QVariantMap jsonMap, QString messageWhenTitled=""); void setIgnoreSslErrors(bool state); void setIgnoreSslInImages(bool state); void setNoHttpsMode(); void setSilentFollows(bool state); void setSilentLists(bool state); void setSilentLikes(bool state); void updatePostsEverSeen(QVariantMap postMap); QVariantMap getPostsEverSeen(); signals: void openingAuthorizeURL(QUrl url); void authorizationFailed(QString errorTitle, QString errorMessage); void authorizationStatusChanged(bool authorized); void initializationStepChanged(int step); void initializationCompleted(); void profileReceived(QString avatarURL, QString fullName, QString hometown, QString bio, QString email); void contactListReceived(QString listType, QVariantList contactList, int totalReceivedCount); void siteUserListReceived(QVariantList contactList, int totalItems); void contactFollowed(ASPerson *contact); void contactUnfollowed(ASPerson *contact); void followingListChanged(); void listsListReceived(QVariantList listsList); void personListReceived(QVariantList personList, QString listUrl); void personAddedToList(QString id, QString name, QString avatarUrl); void personRemovedFromList(QString id); void mainTimelineReceived(QVariantList postList, QString previousLink, QString nextLink, int totalItems); void directTimelineReceived(QVariantList postList, QString previousLink, QString nextLink, int totalItems); void activityTimelineReceived(QVariantList postList, QString previousLink, QString nextLink, int totalItems); void favoritesTimelineReceived(QVariantList postList, QString previousLink, QString nextLink, int totalItems); void userTimelineReceived(QVariantList postList, QString previousLink, QString nextLink, int totalItems, QString url); void userTimelineFailed(); void likesReceived(QVariantList likesList, QString originatingPostURL); void commentsReceived(QVariantList commentsList, QString originatingPostURL); void minorFeedMainReceived(QVariantList activitiesList, QString previousLink, QString nextLink, int totalItemCount); void minorFeedDirectReceived(QVariantList activitiesList, QString previousLink, QString nextLink, int totalItemCount); void minorFeedActivityReceived(QVariantList activitiesList, QString previousLink, QString nextLink, int totalItemCount); void avatarPictureReceived(QByteArray pictureData, QString pictureUrl); void imageReceived(QByteArray pictureData, QString pictureUrl); void imageFailed(QString imageUrl); void downloadCompleted(QString fileUrl); void downloadFailed(QString fileUrl); void avatarStored(QString avatarUrl, QString avatarFilename); void imageStored(QString imageUrl); void postPublished(); void postPublishingFailed(); void likeSet(); void commentPosted(); void commentPostingFailed(); void userDidSomething(); void avatarUploaded(QString url); void showNotification(QString message); void currentJobChanged(QString message); void transientStatusBarMessage(QString message); void logMessage(QString message, QString url=""); public slots: void requestFinished(QNetworkReply *reply); void sslErrorsHandler(QNetworkReply *reply, QList errorList); void getToken(); void authorizeApplication(QString verifierCode); void getInitialData(); void postNote(QMap audienceMap, QString postText, QString postTitle); QNetworkReply *postMedia(QMap audienceMap, QString postText, QString postTitle, QString mediaFilename, QString mediaType, QString mimeContentType); void postMediaStepTwo(QString id); void postAvatarStepTwo(QString id); void updatePost(QString id, QString type, QString content, QString title); void likePost(QString postId, QString postType, QString authorId, bool like); void addComment(QString comment, QString postID, QString postType); void updateComment(QString id, QString content); void sharePost(QString postID, QString postType); void unsharePost(QString postId, QString postType); void deletePost(QString postID, QString postType); void followContact(QString address); void unfollowContact(QString address); private: QNetworkAccessManager nam; QByteArray userAgentString; // QOAuth-related QOAuth::Interface *qoauth; bool isApplicationAuthorized; QString clientID; QString clientSecret; QByteArray token; QByteArray tokenSecret; QString userId; // Full webfinger address, user@host.tld QString userName; QString serverURL; QString apiBaseUrl; QString apiFeedUrl; QString serverScheme; bool proxyUsesAuth; QString userFollowersURL; int userFollowersCount; int userFollowingCount; QStringList followingIdList; int totalReceivedFollowers; int totalReceivedFollowing; QVariantMap postsEverSeen; QTimer *initialDataTimer; int initialDataStep; int initialDataAttempts; bool haveProfile; bool haveFollowing; bool haveFollowers; bool havePersonLists; bool haveMainTL; bool haveDirectTL; bool haveActivityTL; bool haveFavoritesTL; bool haveMainMF; bool haveDirectMF; bool haveActivityMF; int postsPerPageMain; int postsPerPageOther; // For multi-step operations in posts QString currentPostTitle; QString currentPostDescription; QMap currentAudienceMap; QString currentPostType; // Avatars / Images queue QStringList pendingAvatarsList; QStringList pendingImagesList; bool ignoreSslErrors; bool ignoreSslInImages; bool silentFollows; bool silentListsHandling; bool silentLikes; }; #endif // PUMPCONTROLLER_H dianara-v1.3.2/src/post.cpp000644 000764 000764 00000214357 12611531455 015173 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "post.h" #include "mainwindow.h" Post::Post(ASActivity *activity, bool highlightedByFilter, bool isStandalone, PumpController *pumpController, GlobalObject *globalObject, QWidget *parent) : QFrame(parent) { this->pController = pumpController; this->globalObj = globalObject; this->standalone = isStandalone; this->seeFullImageString = tr("Click the image to see it in full size"); this->downloadAttachmentString = tr("Click to download the attachment"); this->postImageIsAnimated = false; // Initialize this->postImageFailed = false; this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); this->setMinimumSize(30, 30); // Ensure something's visible at all times activity->setParent(this); // reparent the passed activity leftColumnFrame = new QFrame(); ///////////////////////////////////////////////// Highlighting this->highlightType = NoHighlight; QStringList highlightColors = globalObj->getColorsList(); QString highlightPostColor; // Post is adressed to us if (activity->getRecipientsIdList().contains("acct:" + pController->currentUserId())) { highlightType = MessageForUserHighlight; highlightPostColor = highlightColors.at(0); // Color #1 in config } else if (activity->author()->getId() == pController->currentUserId() // Post or activity is ours || activity->object()->author()->getId() == pController->currentUserId()) { highlightType = OwnMessageHighlight; highlightPostColor = highlightColors.at(3); // Color #4 in the config } else if (highlightedByFilter) // Highlight by filtering rules { highlightType = FilterRulesHighlight; highlightPostColor = highlightColors.at(4); // Color #5 } if (highlightType != NoHighlight && !standalone // Don't use highlighting when opening as standalone post && !pController->currentUserId().isEmpty()) { if (QColor::isValidColor(highlightPostColor)) { // CSS for horizontal gradient from configured color to transparent QString css = QString("QFrame#LeftFrame " "{ background-color: " " qlineargradient(spread:pad, " " x1:0, y1:0, x2:1, y2:0, " " stop:0 %1, stop:1 rgba(0, 0, 0, 0)); " "}") .arg(highlightPostColor); leftColumnFrame->setObjectName("LeftFrame"); leftColumnFrame->setStyleSheet(css); } else { this->leftColumnFrame->setFrameStyle(QFrame::Panel); } } this->unreadPostColor = highlightColors.at(5); // Color #6 leftColumnLayout = new QVBoxLayout(); this->activityId = activity->getId(); // FIXME: will be empty in Favorites TL this->postId = activity->object()->getId(); this->postType = activity->object()->getType(); this->postUrl = activity->object()->getUrl(); this->postLikesCount = 0; // Store relevant likes/comments/shares URLs this->postLikesUrl = activity->object()->getLikesUrl(); this->postCommentsUrl = activity->object()->getCommentsUrl(); this->postSharesUrl = activity->object()->getSharesUrl(); // Add comments URL to "ever seen" list, only needed if post is on another server if (activity->object()->hasProxiedUrls()) { this->pController->addCommentUrlToSeenList(this->postId, this->postCommentsUrl); } ASPerson *authorPerson; // Having ID means the object has proper author data if (!activity->object()->author()->getId().isEmpty()) { authorPerson = activity->object()->author(); } else // No ID means post author data is the activity's author data { // This is a workaround, because pump.io doesn't give object author // if the activity is by the same user authorPerson = activity->author(); } this->postAuthorId = authorPerson->getId(); this->postAuthorName = authorPerson->getNameWithFallback(); this->postGeneratorString = activity->getGenerator(); // If it's a standalone post, set window title and restore size if (standalone) { QString windowTitle = tr("Post", "Noun, not verb") + ": " + activity->object()->getTranslatedType(postType) + " - Dianara"; this->setWindowTitle(windowTitle); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::WindowModal); // Restore size this->setMinimumSize(420, 360); QSettings settings; this->resize(settings.value("Post/postSize", QSize(600, 440)).toSize()); } postIsUnread = false; postIsDeleted = false; QFont detailsFont; detailsFont.setPointSize(detailsFont.pointSize() - 1); // FIXME: check size first // Is the post a reshare? Indicate who shared it with a wide label at the top postIsSharedLabel = new QLabel(this); // With parent to avoid leaks // Needs to be initialized even if // unused, for setFuzzyTimestamps() postIsSharedLabel->hide(); if (activity->isShared()) { postIsSharedLabel->show(); this->postSharedById = activity->getSharedById(); postShareTime = activity->getUpdatedAt(); QString sharedByName = activity->getSharedByName(); QString sharedByAvatar = MiscHelpers::getCachedAvatarFilename(activity->getSharedByAvatar()); postShareInfoString = QString::fromUtf8("\342\231\272     "); // Recycling symbol QFont shareInfoFont; // If 'extended share info' is enabled, show avatar and use bigger font if (globalObj->getPostExtendedShares()) { // Share info font is standard size postShareInfoString.append(" " "    "); // FIXME/TODO: show client used? } else { // Small font, like in other details shareInfoFont = detailsFont; } postShareInfoString.append(tr("Via %1").arg("author()->getUrl() + "\">" + sharedByName + " - ")); // Fuzzy time is added here, in setFuzzyTimestamps(), along with To/Cc info this->postSharedToCCString.clear(); if (!activity->getToString().isEmpty()) { postSharedToCCString.append(" - " + tr("To") + ": " + activity->getToString()); } if (!activity->getCCString().isEmpty()) { postSharedToCCString.append(" - " + tr("Cc") + ": " + activity->getCCString()); } postIsSharedLabel->setText(postShareInfoString); postIsSharedLabel->setWordWrap(true); postIsSharedLabel->setOpenExternalLinks(true); postIsSharedLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); postIsSharedLabel->setFont(shareInfoFont); postIsSharedLabel->setForegroundRole(QPalette::Text); postIsSharedLabel->setBackgroundRole(QPalette::Base); postIsSharedLabel->setAutoFillBackground(true); postIsSharedLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); connect(postIsSharedLabel, SIGNAL(linkHovered(QString)), this, SLOT(showHighlightedUrl(QString))); QString shareTooltip = "" "" ""; shareTooltip.append("" "" "
" "" "" + sharedByName + "
"); shareTooltip.append("" + this->postSharedById + "" "
"); QString exactShareTime = Timestamp::localTimeDate(postShareTime); shareTooltip.append("
" + tr("Shared on %1").arg(exactShareTime)); if (!this->postGeneratorString.isEmpty()) { shareTooltip.append("
" + tr("Using %1", "1=Program used for posting or sharing") .arg(this->postGeneratorString)); this->postGeneratorString.clear(); // So it's not used in the post timestamp tooltip! } postIsSharedLabel->setToolTip(shareTooltip); } /////////////////////////////////////////////////// Left column, post Meta info // Different frame if post is ours if (postAuthorId == pController->currentUserId()) { postIsOwn = true; qDebug() << "Post is our own!"; this->setFrameStyle(QFrame::Panel | QFrame::Sunken); } else { postIsOwn = false; this->setFrameStyle(QFrame::Box | QFrame::Raised); } // Author avatar QSize avatarSize = globalObj->getPostAvatarSize(); if (!activity->object()->getDeletedTime().isEmpty()) { // If the post was initially deleted, use a small avatar avatarSize = QSize(32,32); } postAuthorAvatarButton = new AvatarButton(authorPerson, this->pController, this->globalObj, avatarSize, this); ///////////// Add extra options to the avatar menu postAuthorAvatarButton->addSeparatorToMenu(); // Open post in browser openPostInBrowserAction = new QAction(QIcon::fromTheme("internet-web-browser", QIcon(":/images/button-download.png")), tr("Open post in web browser"), this); connect(openPostInBrowserAction, SIGNAL(triggered()), this, SLOT(openPostInBrowser())); postAuthorAvatarButton->addActionToMenu(openPostInBrowserAction); // Copy URL copyPostUrlAction = new QAction(QIcon::fromTheme("edit-copy", QIcon(":/images/button-download.png")), tr("Copy post link to clipboard"), this); connect(copyPostUrlAction, SIGNAL(triggered()), this, SLOT(copyPostUrlToClipboard())); postAuthorAvatarButton->addActionToMenu(copyPostUrlAction); // Disable Open and Copy URL if there's no URL if (this->postUrl.isEmpty()) { openPostInBrowserAction->setDisabled(true); copyPostUrlAction->setDisabled(true); } // ----- postAuthorAvatarButton->addSeparatorToMenu(); // Normalize text colors normalizeTextAction = new QAction(QIcon::fromTheme("format-text-color"), tr("Normalize text colors"), this); connect(normalizeTextAction, SIGNAL(triggered()), this, SLOT(normalizeTextFormat())); postAuthorAvatarButton->addActionToMenu(normalizeTextAction); if (standalone) // own window, add close option to menu { // ----- postAuthorAvatarButton->addSeparatorToMenu(); this->closeAction = new QAction(QIcon::fromTheme("window-close", QIcon(":/images/button-close.png")), tr("&Close"), this); connect(closeAction, SIGNAL(triggered()), this, SLOT(close())); postAuthorAvatarButton->addActionToMenu(closeAction); } // End avatar menu leftColumnLayout->addWidget(postAuthorAvatarButton, 0, Qt::AlignLeft); QFont authorFont; authorFont.setBold(true); if (postIsOwn) // Another visual hint when the post is our own { authorFont.setItalic(true); } // Author name postAuthorNameLabel = new QLabel(postAuthorName, this); postAuthorNameLabel->setTextFormat(Qt::PlainText); postAuthorNameLabel->setWordWrap(true /* make optional */); postAuthorNameLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); postAuthorNameLabel->setFont(authorFont); postAuthorNameLabel->setToolTip(authorPerson->getTooltipInfo()); // Ensure a certain width for the metadata column, by setting a minimum width for this label postAuthorNameLabel->setMinimumWidth(90); // FIXME: use font metrics for some chars instead leftColumnLayout->addWidget(postAuthorNameLabel); leftColumnLayout->addSpacing(1); // Post creation time postCreatedAtString = activity->object()->getCreatedAt(); // Timestamp format is "Combined date and time in UTC", like // "2012-02-07T01:32:02Z" as per ISO8601 http://en.wikipedia.org/wiki/ISO_8601 postCreatedAtLabel = new HClabel("", this); postCreatedAtLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); postCreatedAtLabel->setFont(detailsFont); postCreatedAtLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); leftColumnLayout->addWidget(postCreatedAtLabel); // Show generator app, if enabled to display directly if (!this->postGeneratorString.isEmpty() && globalObj->getPostShowExtraInfo()) { //QString::fromUtf8("\342\232\231 ") // Gear symbol this->postGeneratorLabel = new QLabel(QString::fromUtf8("\342\214\250 ") // Keyboard symbol + this->postGeneratorString, this); postGeneratorLabel->setFont(detailsFont); postGeneratorLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum); leftColumnLayout->addWidget(postGeneratorLabel); } leftColumnLayout->addSpacing(4); // Location information, if any this->postLocationLabel = new HClabel("", this); postLocationLabel->setFont(detailsFont); postLocationLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); leftColumnLayout->addWidget(postLocationLabel); leftColumnLayout->addSpacing(2); postLocationLabel->hide(); if (!activity->isShared()) // When shared, To/CC are shown with share info { if (!activity->getToString().isEmpty()) { this->postToLabel = new QLabel(tr("To") + ": " + activity->getToString() + "  "); // Small hack to try to ensure full visibility postToLabel->setWordWrap(true); postToLabel->setOpenExternalLinks(true); postToLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); postToLabel->setFont(detailsFont); leftColumnLayout->addWidget(postToLabel); connect(postToLabel, SIGNAL(linkHovered(QString)), this, SLOT(showHighlightedUrl(QString))); } if (!activity->getCCString().isEmpty()) { this->postCCLabel = new QLabel(tr("Cc") + ": " + activity->getCCString() + "  "); postCCLabel->setWordWrap(true); postCCLabel->setOpenExternalLinks(true); postCCLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); postCCLabel->setFont(detailsFont); leftColumnLayout->addWidget(postCCLabel); connect(postCCLabel, SIGNAL(linkHovered(QString)), this, SLOT(showHighlightedUrl(QString))); } } leftColumnLayout->addSpacing(4); // Set these labels with parent=this, since we're gonna hide them right away // They'll be reparented to the layout, but not having a parent before that // would cause visual glitches postLikesCountLabel = new HClabel("", this); postLikesCountLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); postLikesCountLabel->setFont(detailsFont); postLikesCountLabel->setOpenExternalLinks(true); postLikesCountLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); connect(postLikesCountLabel, SIGNAL(linkHovered(QString)), this, SLOT(showHighlightedUrl(QString))); leftColumnLayout->addWidget(postLikesCountLabel); postLikesCountLabel->hide(); postCommentsCountLabel = new QLabel(this); postCommentsCountLabel->setWordWrap(true); postCommentsCountLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); postCommentsCountLabel->setFont(detailsFont); leftColumnLayout->addWidget(postCommentsCountLabel); postCommentsCountLabel->hide(); postSharesCountLabel = new HClabel("", this); postSharesCountLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); postSharesCountLabel->setFont(detailsFont); postSharesCountLabel->setOpenExternalLinks(true); postSharesCountLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); connect(postSharesCountLabel, SIGNAL(linkHovered(QString)), this, SLOT(showHighlightedUrl(QString))); leftColumnLayout->addWidget(postSharesCountLabel); postSharesCountLabel->hide(); // Try to use all remaining space, aligning // all previous widgets nicely at the top // leftColumnLayout->setAlignment(Qt::AlignTop) caused glitches leftColumnLayout->addStretch(1); QFont buttonsFont; buttonsFont.setPointSize(buttonsFont.pointSize() - 1); // Check if the object is in reply to something (a shared comment, for instance) this->postParentMap = activity->object()->getInReplyTo(); if (!postParentMap.isEmpty()) { openParentPostButton = new QPushButton(QIcon::fromTheme("go-up-search", QIcon(":/images/button-parent.png")), tr("Parent", "As in 'Open the parent post'. " "Try to use the shortest word!")); //openParentPostButton->setFlat(true); openParentPostButton->setFont(buttonsFont); openParentPostButton->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum); openParentPostButton->setToolTip("" + tr("Open the parent post, to which " "this one replies")); connect(openParentPostButton, SIGNAL(clicked()), this, SLOT(openParentPost())); leftColumnLayout->addWidget(openParentPostButton); } // If showing standalone, add a Close button; needed in some environments // Button disabled for now; added option to avatar menu #if 0 if (standalone) { this->closeButton = new QPushButton(QIcon::fromTheme("window-close", QIcon(":/images/button-close.png")), tr("&Close")); closeButton->setFont(buttonsFont); closeButton->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum); connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); leftColumnLayout->addWidget(closeButton); } #endif leftColumnFrame->setLayout(leftColumnLayout); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////// Right column, content rightColumnFrame = new QFrame(); rightColumnFrame->setObjectName("RightFrame"); // For css changes later rightColumnLayout = new QVBoxLayout(); rightColumnLayout->setAlignment(Qt::AlignTop); ///////////////////////////////////// Title this->postTitleLabel = new QLabel(this); postTitleLabel->setWordWrap(true); QFont postTitleFont; postTitleFont.fromString(globalObj->getPostTitleFont()); postTitleLabel->setFont(postTitleFont); rightColumnLayout->addWidget(postTitleLabel, 0, Qt::AlignTop); ///////////////////////////////////// Summary QString postSummary = activity->object()->getSummary(); if (!postSummary.isEmpty()) { this->postSummaryLabel = new QLabel(this); postSummaryLabel->setWordWrap(true); postSummaryLabel->setFont(detailsFont); postSummaryLabel->setText(postSummary); rightColumnLayout->addWidget(postSummaryLabel, 0); } ///////////////////////////////////// Post text postText = new QTextBrowser(this); postText->setAlignment(Qt::AlignLeft | Qt::AlignTop); postText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); postText->setMinimumSize(10, 10); postText->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); postText->setOpenLinks(false); // don't open links, manage in openClickedURL() postText->setReadOnly(true); // it's default with QTextBrowser, but still... QFont postContentsFont; postContentsFont.fromString(globalObj->getPostContentsFont()); postText->setFont(postContentsFont); // To help screen readers, enable keyboard interaction here // This is disabled for now, since it doesn't really help; hopefully with Qt5 // things will be better. //postText->setTextInteractionFlags(Qt::TextBrowserInteraction // | Qt::TextSelectableByKeyboard); connect(postText, SIGNAL(anchorClicked(QUrl)), this, SLOT(openClickedURL(QUrl))); connect(postText, SIGNAL(highlighted(QString)), this, SLOT(showHighlightedUrl(QString))); rightColumnLayout->addWidget(postText, 1); // No alignment; makes size wrong when standalone // Buttons // Add like, comment, share and, if post is ours, edit and delete buttons buttonsLayout = new QHBoxLayout(); buttonsLayout->setAlignment(Qt::AlignHCenter | Qt::AlignTop); buttonsLayout->setContentsMargins(0, 0, 0, 0); buttonsLayout->setMargin(0); buttonsLayout->setSpacing(0); // Like button likeButton = new QPushButton(QIcon::fromTheme("emblem-favorite", QIcon(":/images/button-like.png")), "*like*"); likeButton->setCheckable(true); this->fixLikeButton(activity->object()->isLiked()); likeButton->setFlat(true); likeButton->setFont(buttonsFont); connect(likeButton, SIGNAL(clicked(bool)), this, SLOT(likePost(bool))); buttonsLayout->addWidget(likeButton, 0, Qt::AlignLeft); // Only add Comment and Share buttons if it's NOT a comment if (postType != "comment") // (can happen in the Favorites timeline or via shares) { // Comment (reply) button commentButton = new QPushButton(QIcon::fromTheme("mail-reply-sender", QIcon(":/images/button-comment.png")), tr("Comment", "verb, for the comment button")); commentButton->setToolTip(tr("Reply to this post.") + "
" + tr("If you select some text, " "it will be quoted.")); commentButton->setFlat(true); commentButton->setFont(buttonsFont); connect(commentButton, SIGNAL(clicked()), this, SLOT(commentOnPost())); buttonsLayout->addWidget(commentButton, 0, Qt::AlignLeft); // Share button shareButton = new QPushButton(QIcon::fromTheme("mail-forward", QIcon(":/images/button-share.png")), "*share*"); // Note: Gwenview includes 'document-share' icon shareButton->setFlat(true); shareButton->setFont(buttonsFont); if (postSharedById.isEmpty() || (pController->currentUserId() != this->postSharedById)) { shareButton->setText(tr("Share")); shareButton->setToolTip("" + tr("Share this post with your contacts")); connect(shareButton, SIGNAL(clicked()), this, SLOT(sharePost())); } else // Shared by us! { shareButton->setText(tr("Unshare")); shareButton->setToolTip("" + tr("Unshare this post")); connect(shareButton, SIGNAL(clicked()), this, SLOT(unsharePost())); shareButton->setDisabled(true); // FIXME: disabled for 1.2.x, since it doesn't really work } buttonsLayout->addWidget(shareButton, 0, Qt::AlignLeft); } buttonsLayout->addStretch(1); // so the (optional) Edit and Delete buttons get separated if (postIsOwn) { editButton = new QPushButton(QIcon::fromTheme("document-edit", QIcon(":/images/button-edit.png")), tr("Edit")); editButton->setToolTip("" + tr("Modify this post")); editButton->setFlat(true); editButton->setFont(buttonsFont); connect(editButton, SIGNAL(clicked()), this, SLOT(editPost())); buttonsLayout->addWidget(editButton, 0, Qt::AlignRight); deleteButton = new QPushButton(QIcon::fromTheme("edit-delete", QIcon(":/images/button-delete.png")), tr("Delete")); deleteButton->setToolTip("" + tr("Erase this post")); deleteButton->setFlat(true); deleteButton->setFont(buttonsFont); connect(deleteButton, SIGNAL(clicked()), this, SLOT(deletePost())); buttonsLayout->addWidget(deleteButton, 0, Qt::AlignRight); } /******************************************************************/ this->pendingImagesList.clear(); // Will add the own "post image", and -based ones // Get URL of post image, if it's "image" type of post if (this->postType == "image") { postImageUrl = activity->object()->getImageUrl(); postAttachmentPureUrl = activity->object()->getAttachmentPureUrl(); pendingImagesList.append(postImageUrl); } QString attachmentUrl; // Get URL of post audio file, if it's "audio" type of post if (this->postType == "audio") { postAudioUrl = activity->object()->getAudioUrl(); attachmentUrl = postAudioUrl; postAttachmentPureUrl = activity->object()->getAttachmentPureUrl(); qDebug() << "we got AUDIO:" << postAudioUrl; } // Get URL of post video file, if it's "video" type of post if (this->postType == "video") { postVideoUrl = activity->object()->getVideoUrl(); attachmentUrl = postVideoUrl; postAttachmentPureUrl = activity->object()->getAttachmentPureUrl(); qDebug() << "we got VIDEO:" << postVideoUrl; } // Get URL of post general file, if it's "file" type of post if (this->postType == "file") { postFileUrl = activity->object()->getFileUrl(); attachmentUrl = postFileUrl; postAttachmentPureUrl = activity->object()->getAttachmentPureUrl(); postFileMimeType = activity->object()->getMimeType(); qDebug() << "we got FILE:" << postFileUrl << "; type:" << postFileMimeType; } // Button to join/leave the group, if the post is the creation of a group if (postType == "group") // FIXME: for now, only JOIN, as we can't know if we're members yet { this->joinLeaveButton = new QPushButton(QIcon::fromTheme("user-group-new"), tr("Join Group")); connect(joinLeaveButton, SIGNAL(clicked()), this, SLOT(joinGroup())); rightColumnLayout->addWidget(joinLeaveButton, 0, Qt::AlignCenter); this->groupInfoLabel = new QLabel(tr("%1 members in the group") .arg(activity->object()->getMemberCount())); #ifdef GROUPSUPPORT groupInfoLabel->setText(groupInfoLabel->text() + "
object()->getId() + "\">-GROUP ID-"); #endif groupInfoLabel->setWordWrap(true); groupInfoLabel->setFont(detailsFont); groupInfoLabel->setAlignment(Qt::AlignCenter); rightColumnLayout->addWidget(groupInfoLabel); } // Widget to download the attached media, if any if (!attachmentUrl.isEmpty()) { QString suggestedFilename = MiscHelpers::getSuggestedFilename(this->postAuthorId, this->postType, this->postTitle, this->postAttachmentPureUrl); this->downloadWidget = new DownloadWidget(attachmentUrl, suggestedFilename, this->pController, this); rightColumnLayout->addWidget(downloadWidget, 0); } this->resizesCount = 0; // Post resizing takes place in resizeEvent() // which will also call setPostContents() // FIXME: button creation should be moved here rightColumnLayout->addLayout(buttonsLayout, 0); /////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// Comments block this->commenter = new CommenterBlock(this->pController, this->globalObj, this->postAuthorId, this->standalone, this); connect(commenter, SIGNAL(commentSent(QString)), this, SLOT(sendComment(QString))); connect(commenter, SIGNAL(commentUpdated(QString,QString)), this, SLOT(updateComment(QString,QString))); connect(commenter, SIGNAL(allCommentsRequested()), this, SLOT(getAllComments())); rightColumnLayout->addWidget(commenter, 0); rightColumnLayout->addStretch(0); // Push other stuff to the top if there's extra space rightColumnFrame->setLayout(rightColumnLayout); if (standalone) { // Set link to "check for comments" this->commenter->updateShowAllLink(true); // firstTry=true this->updateBestCommentsUrl(); // If we still don't have a valid URL from our host, proxyURL or not.. if (!pController->urlIsInOurHost(this->postCommentsUrl)) { this->commenter->disableShowAllLink(); } } mainLayout = new QHBoxLayout(); mainLayout->setContentsMargins(0, 0, 0, 0); // Minimal margins mainLayout->setSpacing(1); mainLayout->setAlignment(Qt::AlignLeft | Qt::AlignTop); mainLayout->addWidget(leftColumnFrame, 3); // stretch 3/17 mainLayout->addWidget(rightColumnFrame, 14); // stretch 14/17 if (activity->isShared()) { if (globalObj->getPostExtendedShares()) { // Add a vertical hint on left side, a line and a recycling symbol this->shareHintLabel = new QLabel(QString::fromUtf8("\342\231\272"), this); shareHintLabel->setToolTip(this->postIsSharedLabel->toolTip()); shareHintLabel->setAlignment(Qt::AlignBottom); shareHintLabel->setFrameStyle(QFrame::VLine); mainLayout->insertWidget(0, shareHintLabel); } outerLayout = new QVBoxLayout(); outerLayout->setContentsMargins(0, 0, 0, 0); outerLayout->setSpacing(0); outerLayout->setAlignment(Qt::AlignTop); outerLayout->addWidget(postIsSharedLabel, 0); // Ensure it's small outerLayout->addLayout(mainLayout, 10); this->setLayout(outerLayout); } else { this->setLayout(mainLayout); } // Set read status this->setPostUnreadStatus(); // Set timestamps; Doing this here, after CommenterBlock has been initialized too this->setFuzzyTimestamps(); this->updateDataFromActivity(activity); qDebug() << "Post created" << this->postId; } Post::~Post() { qDebug() << "Post destroyed" << this->postId; } /* * Fill in or replace data such as audience, generator, * or object data from activity data * */ void Post::updateDataFromActivity(ASActivity *activity) { qDebug() << "Updating Post() data from activity..."; // set To/CC..... TODO this->updateDataFromObject(activity->object()); } /* * Fill in or replace data such as timestamps, title, contents, * number of likes and shares, etc, from object data * */ void Post::updateDataFromObject(ASObject *object) { qDebug() << "Updating Post() data from object..."; //////////////////////////////////////////////////////////////// Metadata // Post update time, if any this->postUpdatedAtString = object->getUpdatedAt(); if (this->postUpdatedAtString == this->postCreatedAtString || !object->getDeletedTime().isEmpty()) // Edited time useless when deleted { this->postUpdatedAtString.clear(); } QString postCreatedAtExtendedText = tr("Posted on %1", "1=Date") .arg(Timestamp::localTimeDate(postCreatedAtString)); QString dateGeneratorTooltip = tr("Type", "As in: type of object") + ": " + object->getTranslatedType(postType) + "\n" + postCreatedAtExtendedText; if (!this->postGeneratorString.isEmpty()) { dateGeneratorTooltip.append("\n" + tr("Using %1", "1=Program used for " "posting or sharing") .arg(this->postGeneratorString)); } if (!postUpdatedAtString.isEmpty()) { // Using \n instead of
because I don't want this tooltip to be HTML (wrapped) QString modifiedOn = tr("Modified on %1") .arg(Timestamp::localTimeDate(postUpdatedAtString)); postCreatedAtExtendedText.append("
" + modifiedOn); dateGeneratorTooltip.append("\n\n" + modifiedOn); } postCreatedAtLabel->setExtendedText(postCreatedAtExtendedText); postCreatedAtLabel->setToolTip(dateGeneratorTooltip); this->setFuzzyTimestamps(); // Save post title for openClickedURL(), editPost() etc this->postTitle = object->getTitle(); if (!this->postTitle.isEmpty()) { postTitleLabel->setText(postTitle); postTitleLabel->show(); } else { postTitleLabel->hide(); } // Raw HTML of main content this->postOriginalText = object->getContent(); // Save it for later... QStringList postTextImageList = MiscHelpers::htmlWithReplacedImages(postOriginalText, this->postText->width()); postTextImageList.removeFirst(); // First is the HTML itself qDebug() << "Post has" << postTextImageList.size() << "images included..."; pendingImagesList.append(postTextImageList); this->getPendingImages(); // Location information QString location = object->getLocationName(); if (!location.isEmpty()) { this->postLocationLabel->setBaseText(tr("In") + ": " + location + ""); postLocationLabel->setExtendedText(object->getLocationTooltip()); postLocationLabel->setToolTip("" + QString::fromUtf8("\342\214\226 ") // Position indicator symbol + "" + object->getLocationTooltip()); postLocationLabel->show(); } else { postLocationLabel->hide(); } ///////////////////////////////// Likes - Comments - Shares information /* FIXME: this needs some serious work. * * Separation between what's initially set and what can be * updated later from minor feed information * */ // Set the initial likes, comments and shares (4 most recent) int likesCount = object->getLikesCount().toInt(); if (likesCount > 0) { this->postLikesCount = likesCount; this->setLikesLabel(this->postLikesCount); this->setLikes(object->getLastLikesList(), this->postLikesCount); } int commentsCount = object->getCommentsCount().toInt(); if (commentsCount > 0 && !standalone) // Don't set initial comments in standalone mode { // It's unnecessary and messes up comment area size this->setCommentsLabel(commentsCount); this->commenter->setComments(object->getLastCommentsList(), commentsCount); } int sharesCount = object->getSharesCount().toInt(); this->setSharesLabel(sharesCount); this->setShares(object->getLastSharesList(), sharesCount); // If there's a "deleted" key, show minimalistic version of the post indicating it QString postDeletedTime = object->getDeletedOnString(); if (!postDeletedTime.isEmpty()) { this->setPostDeleted(postDeletedTime); // If the setting to show deleted posts if off, hide if (!globalObj->getShowDeleted()) { // FIXME: this also hides posts that were not deleted when added to the timeline this->hide(); } } this->setPostContents(); } void Post::updateCommentFromObject(ASObject *object) { this->commenter->updateCommentFromObject(object); } void Post::setCommentDeletedFromObject(ASObject *object) { this->commenter->setCommentDeletedFromObject(object); } /* * Set the post contents, and trigger a vertical resize * */ void Post::setPostContents() { //qDebug() << "Post::setPostContents()" << this->postID; QString postMediaHtml; // Embedded image int imageWidth; if (!this->postImageUrl.isEmpty()) { QString imageCachedFilename = MiscHelpers::getCachedImageFilename(postImageUrl); if (QFile::exists(imageCachedFilename)) { // If the image is wider than the post space, make it smaller imageWidth = qMin(MiscHelpers::getImageWidth(imageCachedFilename), this->postWidth); this->postImageIsAnimated = MiscHelpers::isImageAnimated(imageCachedFilename); QString belowMessage; if (postImageIsAnimated) { belowMessage = "" + QString::fromUtf8("\342\226\266") // Play-like symbol + " " + tr("Image is animated. Click on it to play.") + " " + QString::fromUtf8("\342\232\231") // Gear symbol + ""; } else { belowMessage = this->seeFullImageString; } postMediaHtml = MiscHelpers::mediaHtmlBase(this->postType, imageCachedFilename, "", // Could use seeFullImageString in a "novice mode" belowMessage, imageWidth); } else // use placeholder image while it loads, or if it failed... { QString imageLoadingString; QString placeholderFilename; if (this->postImageFailed) { imageLoadingString = tr("Couldn't load image!"); placeholderFilename = "image-missing.png"; } else { imageLoadingString = tr("Loading image..."); placeholderFilename = "image-loading.png"; } postMediaHtml = "
" "
" "" "

" + imageLoadingString + "" "
" "

"; } } // Embedded audio if (!this->postAudioUrl.isEmpty()) { postMediaHtml = MiscHelpers::mediaHtmlBase(this->postType, ":/images/attached-audio.png", this->downloadAttachmentString, tr("Attached Audio")); } // Embedded video if (!this->postVideoUrl.isEmpty()) { postMediaHtml = MiscHelpers::mediaHtmlBase(this->postType, ":/images/attached-video.png", this->downloadAttachmentString, tr("Attached Video")); } // Attached file if (!this->postFileUrl.isEmpty()) { postMediaHtml = MiscHelpers::mediaHtmlBase(this->postType, ":/images/attached-file.png", this->downloadAttachmentString, tr("Attached File") + ": " + postFileMimeType); } // Text contents QStringList postTextImageList = MiscHelpers::htmlWithReplacedImages(postOriginalText, this->postWidth); QString postTextContents = postTextImageList.takeAt(0); // Add the text content of the post postText->setHtml(postMediaHtml + postTextContents); this->setPostHeight(); } void Post::onResizeOrShow() { this->postWidth = qMax(postText->width() - 80, // FIXME: use viewport? 50); // minimum width of 50 this->setPostContents(); this->commenter->adjustCommentsHeight(); this->commenter->adjustCommentArea(); } /* * Set the height of the post, based on the contents * */ void Post::setPostHeight() { // Minimum height depends on configured avatar size int minHeight = qMax(this->globalObj->getPostAvatarSize().height(), 64); // Absolute minimum of 64, even if avatar is 32x32 // Unless the post is deleted, in which case it can be smaller if (postIsDeleted) { minHeight = 20; } int height = qMax(postText->document()->size().toSize().height() + 12, // +12px error margin minHeight); // Don't allow a post to be too tall, either height = qMin(height, this->globalObj->getTimelineHeight()); if (standalone) { postText->setMinimumHeight(minHeight); } else // Only force a specific height if the post is NOT standalone { postText->setMinimumHeight(height); postText->setMaximumHeight(height); } } void Post::resetResizesCount() { this->resizesCount = 0; } /* * Download post image, and img-tag-based images parsed from the contents * */ void Post::getPendingImages() { if (!pendingImagesList.isEmpty()) { foreach (QString imageUrl, pendingImagesList) { pController->enqueueImageForDownload(imageUrl); } connect(pController, SIGNAL(imageStored(QString)), this, SLOT(redrawImages(QString))); connect(pController, SIGNAL(imageFailed(QString)), this, SLOT(onImageFailed(QString))); } } /* * Return the likes/favorites URL for this post * */ QString Post::likesUrl() { return this->postLikesUrl; } /* * Update the tooltip/extended info in "%NUMBER likes" with the names * of the people who liked the post * */ void Post::setLikes(QVariantList likesList, int likesCount) { if (likesList.size() > 0) { this->postLikesMap.clear(); this->postLikesMap = ASObject::simplePersonMapFromList(likesList); this->refreshLikesInfo(likesList.size(), likesCount); } // TMP/FIXME this can set the number to lower than initially set // if called with the "initial" up-to-4 likes list that comes with the post if (likesCount != -1) { this->postLikesCount = likesCount; // use the passed likesCount parameter } else { this->postLikesCount = likesList.size(); // show size of actual names list } this->setLikesLabel(this->postLikesCount); } void Post::appendLike(QString actorId, QString actorName, QString actorUrl) { if (!postLikesMap.contains(actorId)) { ASObject::addOnePersonToSimpleMap(actorId, actorName, actorUrl, &this->postLikesMap); ++this->postLikesCount; this->refreshLikesInfo(this->postLikesCount, this->postLikesCount); // FIXME? this->setLikesLabel(this->postLikesCount); } } void Post::removeLike(QString actorId) { if (postLikesMap.contains(actorId)) { this->postLikesMap.remove(actorId); --this->postLikesCount; this->refreshLikesInfo(this->postLikesCount, this->postLikesCount); // FIXME? this->setLikesLabel(this->postLikesCount); } } void Post::refreshLikesInfo(int likesListSize, int likesCount) { QString peopleString = ASObject::personStringFromSimpleMap(this->postLikesMap, likesCount); QString likesString; if (likesListSize == 1) { likesString = tr("%1 likes this", "One person").arg(peopleString); } else { likesString = tr("%1 like this", "More than one person").arg(peopleString); } this->postLikesCountLabel->setExtendedText(likesString); this->postLikesCountLabel->setToolTip("" // Turn the tooltip into rich text + likesString); } /* * Update the "NUMBER likes" label in left side * */ void Post::setLikesLabel(int likesCount) { if (likesCount != 0) { if (likesCount == 1) { postLikesCountLabel->setBaseText(QString::fromUtf8("\342\231\245 ") // Heart symbol + tr("1 like")); } else { postLikesCountLabel->setBaseText(QString::fromUtf8("\342\231\245 ") // heart symbol + tr("%1 likes").arg(likesCount)); } postLikesCountLabel->show(); } else { postLikesCountLabel->clear(); postLikesCountLabel->hide(); } } /* * Return the comments URL for this post * */ QString Post::commentsURL() { return this->postCommentsUrl; } /* * Ask the Commenter to set new comments * */ void Post::setComments(QVariantList commentsList) { int commentCount = commentsList.size(); this->commenter->setComments(commentsList, commentCount); this->commenter->adjustCommentArea(); // update number of comments in left side counter this->setCommentsLabel(commentCount); } void Post::appendComment(ASObject *comment) { this->commenter->appendComment(comment, true); // Just this one this->commenter->adjustCommentsHeight(); this->commenter->adjustCommentArea(); this->setCommentsLabel(this->commenter->getCommentCount()); } /* * Update the "NUMBER comments" label in left side * */ void Post::setCommentsLabel(int commentsCount) { if (commentsCount != 0) { QString countString = QString::fromUtf8("\342\234\215 "); // writing hand if (commentsCount == 1) { countString.append(tr("1 comment")); } else { countString.append(tr("%1 comments").arg(commentsCount)); } postCommentsCountLabel->setText(countString); postCommentsCountLabel->show(); } else { postCommentsCountLabel->clear(); postCommentsCountLabel->hide(); } } void Post::updateBestCommentsUrl() { // If comments URL is in another server and not proxyed... if (!pController->urlIsInOurHost(this->postCommentsUrl)) { // Try to find the post in the 'ever seen' list QString newCommentsUrl = pController->commentsUrlForPost(this->postId); if (!newCommentsUrl.isEmpty()) { qDebug() << "Found matching post; Updating comments URL..."; this->postCommentsUrl = newCommentsUrl; } } } /* * Return the shares URL for this post, * list of people who reshared it * */ QString Post::sharesURL() { return this->postSharesUrl; } /* * Update the tooltip for "%NUMBER shares" with names * */ void Post::setShares(QVariantList sharesList, int sharesCount) { if (sharesList.size() > 0) { QString peopleString = ASObject::personStringFromList(sharesList, sharesCount); QString sharesString; if (sharesList.size() == 1) { sharesString = tr("%1 shared this", "%1 = One person name").arg(peopleString); } else { sharesString = tr("%1 shared this", "%1 = Names for more than one person").arg(peopleString); } this->postSharesCountLabel->setExtendedText(sharesString); this->postSharesCountLabel->setToolTip("" // So the tooltip is rich text, wordwrapped + sharesString); } // TMP/FIXME this can set the number to lower than initially set // if called with the "initial" up-to-4 shares list that comes with the post if (sharesCount != -1) { this->setSharesLabel(sharesCount); // use the passed sharesCount parameter } else { this->setSharesLabel(sharesList.size()); // show size of actual names list } } void Post::setSharesLabel(int resharesCount) { if (resharesCount != 0) { if (resharesCount == 1) { postSharesCountLabel->setBaseText(QString::fromUtf8("\342\231\273 ") // Recycle symbol + tr("Shared once")); } else { postSharesCountLabel->setBaseText(QString::fromUtf8("\342\231\273 ") // Recycle symbol + tr("Shared %1 times").arg(resharesCount)); } postSharesCountLabel->show(); } else { postSharesCountLabel->clear(); postSharesCountLabel->hide(); } } /* * Set or unset the visual hint indicating if the post is unread * */ void Post::setPostUnreadStatus() { if (!QColor::isValidColor(unreadPostColor)) { unreadPostColor = "palette(highlight)"; } // CSS for horizontal gradient from configured color to transparent QString css = QString("QFrame#RightFrame " "{ background-color: " " qlineargradient(spread:pad, " " x1:0, y1:0, x2:1, y2:0, " " stop:0 rgba(0, 0, 0, 0), " " stop:1 %1); " "}").arg(unreadPostColor); if (this->postIsUnread) { rightColumnFrame->setStyleSheet(css); postCreatedAtLabel->setHighlighted(true); } else { rightColumnFrame->setStyleSheet(""); postCreatedAtLabel->setHighlighted(false); } // Avoid flickering effect later this->postCreatedAtLabel->repaint(); } void Post::setPostAsNew() { this->postIsUnread = true; setPostUnreadStatus(); } void Post::setPostAsRead(bool informTimeline) { if (postIsUnread) { this->postIsUnread = false; if (informTimeline) { // Inform the Timeline() if (this->highlightType == NoHighlight) { emit postRead(false); } else { emit postRead(true); // Say it's marked as read, and was highlighted } } setPostUnreadStatus(); } } void Post::setPostDeleted(QString postDeletedTime) { if (this->postIsDeleted) { // Already set as deleted, ignore return; } /// FIXME, this needs some work if (!this->postOriginalText.isEmpty()) { this->postOriginalText.prepend("
"); // -------- } this->postOriginalText.prepend("
" + postDeletedTime + "
"); // kinda tmp... qDebug() << "This post was deleted on" << postDeletedTime; this->postIsDeleted = true; this->setDisabled(true); this->setPostContents(); } int Post::getHighlightType() { return this->highlightType; } QString Post::getActivityId() { return this->activityId; } QString Post::getObjectId() { return this->postId; } void Post::setFuzzyTimestamps() { QString fuzzyTimestamps = Timestamp::fuzzyTime(postCreatedAtString); if (!postUpdatedAtString.isEmpty()) { fuzzyTimestamps.append("
" + tr("Edited: %1") .arg(Timestamp::fuzzyTime(postUpdatedAtString))); } this->postCreatedAtLabel->setBaseText(fuzzyTimestamps); // Update "share time" on share info too! if (!postShareInfoString.isEmpty()) { QString shareLabelString; shareLabelString = this->postShareInfoString; shareLabelString.append(Timestamp::fuzzyTime(postShareTime)); shareLabelString.append(this->postSharedToCCString); shareLabelString.append(QString::fromUtf8("     " "\342\231\272")); // Recycle symbol, again this->postIsSharedLabel->setText(shareLabelString); } commenter->updateFuzzyTimestamps(); } void Post::syncAvatarFollowState() { this->postAuthorAvatarButton->syncFollowState(); // Ask CommenterBlock to update its avatars too this->commenter->updateAvatarFollowStates(); } bool Post::isBeingCommented() { if (this->commenter->isFullMode()) { return true; } return false; } bool Post::isNew() { return this->postIsUnread; } //////////////////////////////////////////////////////////////////////////// ////////////////////////////////// SLOTS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////// /* * Like (favorite) a post * */ void Post::likePost(bool like) { qDebug() << "Post::likePost()" << (like ? "like" : "unlike"); this->pController->likePost(this->postId, this->postType, this->postAuthorId, like); this->fixLikeButton(like ? "true" : "false"); connect(pController, SIGNAL(likeSet()), this, SLOT(getAllLikes())); } /* * Set the right labels and tooltips to the like button, depending on its state * */ void Post::fixLikeButton(QString state) { if (state == "true") { likeButton->setToolTip("" + tr("You like this")); likeButton->setText(tr("Unlike")); likeButton->setChecked(true); } else { likeButton->setToolTip("" + tr("Like this post")); likeButton->setText(tr("Like")); likeButton->setChecked(false); } } void Post::getAllLikes() { disconnect(pController, SIGNAL(likeSet()), this, SLOT(getAllLikes())); this->pController->getPostLikes(this->postLikesUrl); } /* * Make the commenter widget visible, so user can type the comment * * If some text was selected, insert it as quoted text * */ void Post::commentOnPost() { qDebug() << "Commenting on post" << this->postTitle << this->postId; QString initialText; QString selectedText = this->postText->textCursor().selectedText(); if (!selectedText.isEmpty()) { // Selected text has literal < and >, so convert to HTML entities selectedText.replace("<", "<"); selectedText.replace(">", ">"); selectedText.prepend("[...] "); selectedText.append(" [...]"); initialText = MiscHelpers::quotedText(this->postAuthorName, selectedText); } this->commenter->setFullMode(initialText); emit commentingOnPost(this->commenter); } /* * The actual sending of the comment to the Pump controller * */ void Post::sendComment(QString commentText) { qDebug() << "About to publish this comment:" << commentText; this->pController->addComment(MiscHelpers::cleanupHtml(commentText), this->postId, this->postType); } void Post::updateComment(QString commentId, QString commentText) { this->pController->updateComment(commentId, MiscHelpers::cleanupHtml(commentText)); } void Post::getAllComments() { this->updateBestCommentsUrl(); /* // Keeping this for the future * // to be used to copy over comments directly (TBD) // Try to find this same post in one of the major timelines qDebug() << "Looking for this post in the major timelines, for comments"; MainWindow *mainWindow = (MainWindow *)this->globalObj->parent(); // Kinda risky bool postFoundOk = false; Post *alternatePost = mainWindow->findPostInTimelines(this->postId, &postFoundOk); if (postFoundOk) { qDebug() << "Found the post; Updating comments Url..."; this->postCommentsUrl = alternatePost->commentsURL(); } */ this->pController->getPostComments(this->postCommentsUrl); } /* * Set all comments received from signal, when post is a separate window, * and not handled by Timeline() * */ void Post::setAllComments(QVariantList commentsList, QString originatingPostUrl) { QString originatingPostCleanUrl = originatingPostUrl.split("?").first(); if (this->commentsURL() == originatingPostCleanUrl) { this->setComments(commentsList); } } /* * Re-share a post * */ void Post::sharePost() { int confirmation = QMessageBox::question(this, tr("Share post?"), tr("Do you want to share %1's post?") .arg(this->postAuthorName), tr("&Yes, share it"), tr("&No"), "", 1, 1); if (confirmation == 0) { qDebug() << "Sharing this post:" << this->postId; this->pController->sharePost(this->postId, this->postType); } else { qDebug() << "Confirmation cancelled, not sharing"; } } void Post::unsharePost() { int confirmation = QMessageBox::question(this, tr("Unshare post?"), tr("Do you want to unshare %1's post?") .arg(this->postAuthorName), tr("&Yes, unshare it"), tr("&No"), "", 1, 1); if (confirmation == 0) { qDebug() << "Unsharing this post:" << this->postId; this->pController->unsharePost(this->postId, this->postType); this->setDisabled(true); // Disable the widget, to let user know it's been unshared } else { qDebug() << "Confirmation cancelled, will not unshare"; } } /* * Set the Publisher in editing mode with this post's contents * */ void Post::editPost() { this->globalObj->editPost(this->postId, this->postType, this->postTitle, this->postOriginalText); if (standalone) { this->close(); } } /* * Delete a post * */ void Post::deletePost() { int confirmation = QMessageBox::question(this, tr("WARNING: Delete post?"), tr("Are you sure you want to " "delete this post?"), tr("&Yes, delete it"), tr("&No"), "", 1, 1); if (confirmation == 0) { qDebug() << "Deleting post"; this->pController->deletePost(this->postId, this->postType); QString timeNow = QDateTime::currentDateTimeUtc().toString(Qt::ISODate); this->setPostDeleted(ASObject::makeDeletedOnString(timeNow)); } else { qDebug() << "Confirmation cancelled, not deleting the post"; } } void Post::joinGroup() { qDebug() << "Joining group " << this->postId << "via Post() button"; this->pController->joinGroup(this->postId); this->joinLeaveButton->setDisabled(true); // FIXME; make it turn into "Leave", etc. } void Post::openClickedURL(QUrl url) { qDebug() << "Anchor URL clicked:" << url.toString(); if (url.scheme() == "image") // Use our own viewer, or maybe a configured external viewer { qDebug() << "Opening this image in our own viewer..."; QString suggestedFilename = MiscHelpers::getSuggestedFilename(this->postAuthorId, this->postType, this->postTitle, this->postAttachmentPureUrl); ImageViewer *viewer = new ImageViewer(url.toString(), this->postTitle, suggestedFilename, this->postImageIsAnimated); viewer->show(); } else if (url.scheme() == "attachment") { this->downloadWidget->downloadAttachment(); } else { qDebug() << "Opening this link in browser"; QDesktopServices::openUrl(url); } } void Post::showHighlightedUrl(QString url) { if (!url.isEmpty()) { if (url.startsWith("image:/")) // Own image:/ URL { this->pController->showTransientMessage(this->seeFullImageString); } else if (url.startsWith("attachment:/")) { this->pController->showTransientMessage(this->downloadAttachmentString); } else // Normal URL { this->pController->showTransientMessage(url); qDebug() << "Highlighted url in post:" << url; } } else { this->pController->showTransientMessage(""); } } void Post::openPostInBrowser() { QDesktopServices::openUrl(this->postUrl); } void Post::copyPostUrlToClipboard() { QApplication::clipboard()->setText(this->postUrl); } void Post::openParentPost() { qDebug() << "Opening parent post..."; // Create a fake activity for the parent post QVariantMap fakeActivityMap; fakeActivityMap.insert("object", this->postParentMap); fakeActivityMap.insert("actor", this->postParentMap.value("author").toMap()); ASActivity *originalPostActivity = new ASActivity(fakeActivityMap, this); Post *parentPost = new Post(originalPostActivity, false, // Not highlighted true, // Post is standalone pController, globalObj, this->parentWidget()); // Pass parent widget (Timeline or // another Post) instead of // 'this', so it won't be killed by reloads parentPost->show(); connect(pController, SIGNAL(commentsReceived(QVariantList,QString)), parentPost, SLOT(setAllComments(QVariantList,QString))); parentPost->getAllComments(); } /* * Normalize text colors; Used from menu when a post * has white text with white background, or similar * */ void Post::normalizeTextFormat() { postText->selectAll(); // Set default text color for all text postText->setTextColor(qApp->palette().windowText().color()); postText->setTextBackgroundColor(QColor(Qt::transparent)); // Take care of background colors QTextBlockFormat blockFormat = postText->textCursor().blockFormat(); blockFormat.setBackground(QBrush()); postText->textCursor().setBlockFormat(blockFormat); // Select 'none' postText->moveCursor(QTextCursor::Start); postText->textCursor().select(QTextCursor::WordUnderCursor); } /* * Trigger a resizeEvent, which will call * setPostContents() and setPostHeight() * */ void Post::triggerResize() { this->resize(this->width() - 1, this->height() - 1); } /* * Redraw post contents after receiving downloaded images * */ void Post::redrawImages(QString imageUrl) { if (pendingImagesList.contains(imageUrl)) { this->pendingImagesList.removeAll(imageUrl); // If there are no more images for this post, disconnect if (pendingImagesList.isEmpty()) { disconnect(pController, SIGNAL(imageStored(QString)), this, SLOT(redrawImages(QString))); disconnect(pController, SIGNAL(imageFailed(QString)), this, SLOT(onImageFailed(QString))); // Trigger resizeEvent(), with setPostContents(), etc. triggerResize(); } } } void Post::onImageFailed(QString imageUrl) { if (imageUrl == this->postImageUrl) { this->postImageFailed = true; } this->redrawImages(imageUrl); } //////////////////////////////////////////////////////////////////////////// //////////////////////////////// PROTECTED ///////////////////////////////// //////////////////////////////////////////////////////////////////////////// void Post::resizeEvent(QResizeEvent *event) { //qDebug() << "Post::resizeEvent()" << event->size(); if (this->resizesCount > 10) // Don't resize more than 10 times in a row { qDebug() << "Post::resizeEvent(); Too many resizes, too fast; ignoring!"; event->ignore(); return; } this->onResizeOrShow(); // Standalone mode doesn't need this kind of protection, and is not re-set by Timeline() if (!standalone) { ++this->resizesCount; } event->accept(); } /* * On mouse click in any part of the post, set it as read * */ void Post::mousePressEvent(QMouseEvent *event) { setPostAsRead(); event->accept(); } void Post::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { event->accept(); if (standalone) { this->close(); } } else { event->ignore(); } } /* * Ensure we change back the highlighted URL message when the mouse leaves the post * */ void Post::leaveEvent(QEvent *event) { this->pController->showTransientMessage(""); event->accept(); } /* * closeEvent, needed when posts are opened in separate window * */ void Post::closeEvent(QCloseEvent *event) { qDebug() << "Post::closeEvent()"; if (standalone) { QSettings settings; if (settings.isWritable()) { settings.setValue("Post/postSize", this->size()); } } if (this->commenter->isFullMode()) { // Ask composer to cancel post, which asks the user, unless empty this->commenter->getComposer()->cancelPost(); } event->ignore(); // Event will be ignored anyway // Check again; if it's still full, it means user cancelled if (!this->commenter->isFullMode()) // If not, accepted, so kill the post { this->hide(); this->deleteLater(); } } /* * Needed when a post is shown as a standalone window, * to ensure images are properly resized * */ void Post::showEvent(QShowEvent *event) { if (standalone) { this->onResizeOrShow(); } event->accept(); } dianara-v1.3.2/src/images/000755 000764 000764 00000000000 12157073165 014737 5ustar00janjan000000 000000 dianara-v1.3.2/src/profileeditor.h000664 000764 000764 00000005006 12537144205 016511 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef PROFILEEDITOR_H #define PROFILEEDITOR_H #include #include #include #include #include #include #include #include #include #include #include #include #include "pumpcontroller.h" #include "mischelpers.h" #include "emailchanger.h" class ProfileEditor : public QWidget { Q_OBJECT public: explicit ProfileEditor(PumpController *pumpController, QWidget *parent = 0); ~ProfileEditor(); void setProfileData(QString avatarUrl, QString fullName, QString hometown, QString bio, QString eMail); void toggleWidgetsEnabled(bool state); signals: public slots: void findAvatarFile(); void saveProfile(); void sendProfileData(QString newImageUrl = QString()); void enableSaveButton(); protected: virtual void closeEvent(QCloseEvent *event); private: PumpController *pController; QVBoxLayout *mainLayout; QFormLayout *topLayout; QHBoxLayout *emailLayout; QHBoxLayout *avatarLayout; QHBoxLayout *bottomLayout; QLabel *avatarLabel; QPushButton *changeAvatarButton; bool avatarChanged; QLabel *webfingerLabel; QLabel *emailLabel; QPushButton *changeEmailButton; EmailChanger *emailChanger; QLineEdit *fullNameLineEdit; QLineEdit *hometownLineEdit; QTextEdit *bioTextEdit; QPushButton *saveButton; QPushButton *cancelButton; QAction *cancelAction; QString currentAvatarURL; QString newAvatarFilename; QString newAvatarContentType; }; #endif // PROFILEEDITOR_H dianara-v1.3.2/src/publisher.h000664 000764 000764 00000011400 12563407171 015635 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef PUBLISHER_H #define PUBLISHER_H #include #include #include #include #include #include #include #include #include #include #include #include "composer.h" #include "pumpcontroller.h" #include "globalobject.h" #include "mischelpers.h" #include "audienceselector.h" class Publisher : public QWidget { Q_OBJECT public: explicit Publisher(PumpController *pumpController, GlobalObject *globalObject, QWidget *parent = 0); ~Publisher(); void syncFromConfig(); void setEmptyMediaData(); void setTitleAndContent(QString title, QString content); void updatePublicFollowersLabels(); QMap getAudienceMap(); void setMediaModeWidgets(); void toggleWidgetsWhileSending(bool widgetsEnabled); bool isFullMode(); signals: public slots: void setMinimumMode(); void setFullMode(); void setPictureMode(); void setAudioMode(); void setVideoMode(); void setFileMode(); void cancelMediaMode(); void setEditingMode(QString postId, QString postType, QString postTitle, QString postText); void startMessageForContact(QString id, QString name, QString url); void addNickToRecipients(QString id, QString name, QString url); void onPublishingOk(); void onPublishingFailed(); void setToPublic(bool activated); void setToFollowers(bool activated); void setCCPublic(bool activated); void setCCFollowers(bool activated); void updateToCcFields(QString selectorType, QStringList contactsList, QStringList urlsList); void updateListsMenus(QVariantList listsList); void updateToListsFields(QAction *listAction); void updateCcListsFields(QAction *listAction); void showHighlightedUrl(QString url); void sendPost(); void findMediaFile(); void updateProgressBar(qint64 sent, qint64 total); void updateCharacterCounter(); private: QGridLayout *mainLayout; QHBoxLayout *titleLayout; QLabel *titleLabel; QLineEdit *titleLineEdit; QLabel *mediaInfoLabel; QPushButton *selectMediaButton; QPushButton *removeMediaButton; QLabel *pictureLabel; QProgressBar *uploadProgressBar; QPushButton *toolsButton; Composer *composerBox; QPushButton *toSelectorButton; QAction *toPublicAction; QAction *toFollowersAction; QMenu *toSelectorMenu; QMenu *toSelectorListsMenu; QPushButton *ccSelectorButton; QAction *ccPublicAction; QAction *ccFollowersAction; QMenu *ccSelectorMenu; QMenu *ccSelectorListsMenu; AudienceSelector *audienceSelectorTo; AudienceSelector *audienceSelectorCC; QLabel *toPublicFollowersLabel; QLabel *toAudienceLabel; QLabel *ccPublicFollowersLabel; QLabel *ccAudienceLabel; QStringList toAddressStringList; QStringList ccAddressStringList; QStringList toListsNameStringList; QStringList toListsIdStringList; QStringList ccListsNameStringList; QStringList ccListsIdStringList; #ifdef GROUPSUPPORT QLabel *groupIdLabel; QLineEdit *groupIdLineEdit; #endif QPushButton *addMediaButton; QMenu *addMediaMenu; QAction *addMediaImageAction; QAction *addMediaAudioAction; QAction *addMediaVideoAction; QAction *addMediaFileAction; QLabel *charCounterLabel; QLabel *statusInfoLabel; QPushButton *postButton; QPushButton *cancelButton; bool defaultPublicPosting; bool showCharacterCounter; bool onlyToFollowers; QString mediaFilename; QString mediaContentType; QString lastUsedDirectory; QString postType; bool editingMode; QString editingPostId; bool fullMode; QNetworkReply *uploadNetworkReply; PumpController *pController; GlobalObject *globalObj; }; #endif // PUBLISHER_H dianara-v1.3.2/src/notifications.h000644 000764 000764 00000004205 12451104142 016500 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef NOTIFICATIONS_H #define NOTIFICATIONS_H #include #include #include #include #ifdef QT_DBUS_LIB #include #endif #include class FDNotifications : public QObject { Q_OBJECT public: FDNotifications(QObject* parent); ~FDNotifications(); bool areNotificationsAvailable(); void setNotificationOptions(int style, bool notifyNewTL, bool notifyHLTL, bool notifyNewMW, bool notifyHLMW); void setCurrentUserId(QString newId); bool getNotifyNewTimeline(); bool getNotifyHLTimeline(); bool getNotifyNewMeanwhile(); bool getNotifyHLMeanwhile(); enum notificationTypes { SystemNotifications, FallbackNotifications, NoNotifications }; signals: void showFallbackNotification(QString title, QString message); public slots: void showMessage(QString message); private: #ifdef QT_DBUS_LIB QDBusConnection *bus; #endif bool notificationsAvailable; int notificationType; bool notifyNewTimeline; bool notifyHLTimeline; bool notifyNewMeanwhile; bool notifyHLMeanwhile; QString currentUserId; }; #endif // NOTIFICATIONS_H dianara-v1.3.2/src/notifications.cpp000644 000764 000764 00000013745 12611532223 017047 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "notifications.h" FDNotifications::FDNotifications(QObject* parent) : QObject(parent) { qDebug() << "Creating FreeDesktop Notifier"; this->notificationsAvailable = false; // Init as false, detect later #ifdef QT_DBUS_LIB this->bus = new QDBusConnection(QDBusConnection::sessionBus()); if (bus->isConnected()) { QDBusReply busReply = bus->interface()->registeredServiceNames(); qDebug() << "Listing D-Bus services..."; if (busReply.isValid()) { foreach (QString serviceName, busReply.value()) { if (serviceName == "org.freedesktop.Notifications") { qDebug() << "org.freedesktop.Notifications D-Bus service found!"; this->notificationsAvailable = true; } } } } else { qDebug() << "D-Bus unavailable!"; notificationsAvailable = false; } #endif qDebug() << "FreeDesktop Notifier created"; } FDNotifications::~FDNotifications() { #ifdef QT_DBUS_LIB delete bus; #endif qDebug() << "FreeDesktop Notifier destroyed"; } bool FDNotifications::areNotificationsAvailable() { qDebug() << "Are notifications available?" << notificationsAvailable; return notificationsAvailable; } void FDNotifications::setNotificationOptions(int style, bool notifyNewTL, bool notifyHLTL, bool notifyNewMW, bool notifyHLMW) { this->notificationType = style; this->notifyNewTimeline = notifyNewTL; this->notifyHLTimeline = notifyHLTL; this->notifyNewMeanwhile = notifyNewMW; this->notifyHLMeanwhile = notifyHLMW; qDebug() << "Notification type set to" << this->notificationType << "-- FD.o/Qt/none"; qDebug() << this->notifyNewTimeline << this->notifyHLTimeline << this->notifyNewMeanwhile << this->notifyHLMeanwhile; } void FDNotifications::setCurrentUserId(QString newId) { this->currentUserId = newId; } bool FDNotifications::getNotifyNewTimeline() { return this->notifyNewTimeline; } bool FDNotifications::getNotifyHLTimeline() { return this->notifyHLTimeline; } bool FDNotifications::getNotifyNewMeanwhile() { return this->notifyNewMeanwhile; } bool FDNotifications::getNotifyHLMeanwhile() { return this->notifyHLMeanwhile; } /////////////////////////////////////////////////////////////////////// ////////////////////////////// SLOTS ////////////////////////////////// /////////////////////////////////////////////////////////////////////// void FDNotifications::showMessage(QString message) { // if notifications are disabled if (this->notificationType == FDNotifications::NoNotifications) { return; } QString notificationTitle = "Dianara - " + currentUserId; // If FD.org notifications are not available, or Qt's Balloon ones are selected if (!notificationsAvailable || notificationType == FDNotifications::FallbackNotifications) { qDebug() << "FreeDesktop Notifications are NOT available, " "or balloon notifications selected"; // Clean up possible HTML, since Qt's balloons don't support it message.remove(""); message.remove(""); message.remove(""); message.remove(""); message.remove(""); message.remove(""); message.replace("
", "\n"); // FIXME: remove also emit showFallbackNotification(notificationTitle, message); return; } // Only when building with D-Bus support #ifdef QT_DBUS_LIB message.replace("\n", "
"); // use HTML newlines // HTML newlines break notifications in Xfce; \n works, but is not actually // shown as newline in Plasma 4's notifications QDBusMessage dBusMessage = QDBusMessage::createMethodCall( "org.freedesktop.Notifications", "/org/freedesktop/Notifications", "", "Notify"); // --- D-Bus Notify call ---------------------------------------------- // method uint org.freedesktop.Notifications.Notify(QString app_name, // uint replaces_id, QString app_icon, QString summary, QString body, // QStringList actions, QVariantMap hints, int timeout) QList arguments; arguments << QString("Dianara"); // app_name arguments << uint(0); // replaces_id if (QIcon::hasThemeIcon("dianara")) { arguments << QString("dianara"); // app_icon, if "dianara" icon is in theme } else { arguments << QString("dialog-information"); // app_icon otherwise } arguments << notificationTitle; arguments << message; arguments << QStringList(); // actions arguments << QVariantMap(); // hints arguments << 4000; // timeout, in milliseconds dBusMessage.setArguments(arguments); qDebug() << "sending DBUS call to org.freedesktop.Notifications Notify"; this->bus->asyncCall(dBusMessage); #endif } dianara-v1.3.2/src/post.h000644 000764 000764 00000015317 12604247647 014644 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef POST_H #define POST_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pumpcontroller.h" #include "globalobject.h" #include "timestamp.h" #include "mischelpers.h" #include "commenterblock.h" #include "imageviewer.h" #include "asactivity.h" #include "avatarbutton.h" #include "downloadwidget.h" #include "hclabel.h" class Post : public QFrame { Q_OBJECT public: Post(ASActivity *activity, bool highlightedByFilter, bool isStandalone, PumpController *pumpController, GlobalObject *globalObject, QWidget *parent); ~Post(); void updateDataFromActivity(ASActivity *activity); void updateDataFromObject(ASObject *object); void updateCommentFromObject(ASObject *object); void setCommentDeletedFromObject(ASObject *object); void setPostContents(); void onResizeOrShow(); void setPostHeight(); void resetResizesCount(); void getPendingImages(); QString likesUrl(); void setLikes(QVariantList likesList, int likesCount=-1); void appendLike(QString actorId, QString actorName, QString actorUrl); void removeLike(QString actorId); void refreshLikesInfo(int likesListSize, int likesCount); void setLikesLabel(int likesCount); QString commentsURL(); void setComments(QVariantList commentsList); void appendComment(ASObject *comment); void setCommentsLabel(int commentsCount); void updateBestCommentsUrl(); QString sharesURL(); void setShares(QVariantList sharesList, int sharesCount=-1); void setSharesLabel(int resharesCount); void setPostUnreadStatus(); void setPostAsNew(); void setPostAsRead(bool informTimeline=true); void setPostDeleted(QString postDeletedTime); int getHighlightType(); QString getActivityId(); QString getObjectId(); void setFuzzyTimestamps(); void syncAvatarFollowState(); bool isBeingCommented(); bool isNew(); enum PostHightlightType { NoHighlight = -1, MessageForUserHighlight, OwnMessageHighlight, FilterRulesHighlight }; signals: void postRead(bool wasHighlighted); void commentingOnPost(QWidget *commenterWidget); public slots: void likePost(bool like); void fixLikeButton(QString state); void getAllLikes(); void commentOnPost(); void sendComment(QString commentText); void updateComment(QString commentId, QString commentText); void getAllComments(); void setAllComments(QVariantList commentsList, QString originatingPostUrl); void sharePost(); void unsharePost(); void editPost(); void deletePost(); void joinGroup(); void openClickedURL(QUrl url); void showHighlightedUrl(QString url); void openPostInBrowser(); void copyPostUrlToClipboard(); void openParentPost(); void normalizeTextFormat(); void triggerResize(); void redrawImages(QString imageUrl); void onImageFailed(QString imageUrl); protected: virtual void resizeEvent(QResizeEvent *event); virtual void mousePressEvent(QMouseEvent *event); virtual void keyPressEvent(QKeyEvent *event); virtual void leaveEvent(QEvent *event); virtual void closeEvent(QCloseEvent *event); virtual void showEvent(QShowEvent *event); private: QHBoxLayout *mainLayout; QVBoxLayout *leftColumnLayout; QVBoxLayout *rightColumnLayout; QVBoxLayout *outerLayout; QFrame *leftColumnFrame; QFrame *rightColumnFrame; QString activityId; QString postId; QString postType; QString postUrl; QString postAuthorId; QString postAuthorName; QString postSharedById; QString postShareInfoString; QString postSharedToCCString; QString postShareTime; bool postIsOwn; bool postIsUnread; bool postIsDeleted; int highlightType; QString unreadPostColor; AvatarButton *postAuthorAvatarButton; QAction *openPostInBrowserAction; QAction *copyPostUrlAction; QAction *normalizeTextAction; QAction *closeAction; QLabel *postAuthorNameLabel; HClabel *postCreatedAtLabel; QLabel *postGeneratorLabel; HClabel *postLocationLabel; QLabel *postToLabel; QLabel *postCCLabel; QLabel *postIsSharedLabel; QLabel *shareHintLabel; HClabel *postLikesCountLabel; QLabel *postCommentsCountLabel; HClabel *postSharesCountLabel; QPushButton *openParentPostButton; //QPushButton *closeButton; QLabel *postTitleLabel; QLabel *postSummaryLabel; QTextBrowser *postText; QHBoxLayout *buttonsLayout; QPushButton *likeButton; QPushButton *commentButton; QPushButton *shareButton; QPushButton *editButton; QPushButton *deleteButton; QPushButton *joinLeaveButton; QLabel *groupInfoLabel; DownloadWidget *downloadWidget; QString postTitle; QString postImageUrl; bool postImageIsAnimated; bool postImageFailed; QString postAudioUrl; QString postVideoUrl; QString postFileUrl; QString postFileMimeType; QString postAttachmentPureUrl; QString postOriginalText; QVariantMap postParentMap; QString postCreatedAtString; QString postUpdatedAtString; QString postGeneratorString; int postWidth; int resizesCount; QString postLikesUrl; int postLikesCount; QVariantMap postLikesMap; QString postCommentsUrl; QString postSharesUrl; QStringList pendingImagesList; bool standalone; QString seeFullImageString; QString downloadAttachmentString; PumpController *pController; GlobalObject *globalObj; CommenterBlock *commenter; }; #endif // POST_H dianara-v1.3.2/src/timeline.h000644 000764 000764 00000012413 12602770340 015444 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef TIMELINE_H #define TIMELINE_H #include #include #include #include #include #include #include #include #include "pumpcontroller.h" #include "globalobject.h" #include "asobject.h" #include "asactivity.h" #include "post.h" #include "filterchecker.h" #include "pageselector.h" class TimeLine : public QWidget { Q_OBJECT public: TimeLine(PumpController::requestTypes timelineType, PumpController *pumpController, GlobalObject *globalObject, FilterChecker *filterChecker, QWidget *parent = 0); ~TimeLine(); void setCustomUrl(QString url); void clearTimeLineContents(bool showMessage=true); void removeOldPosts(int minimumToKeep); void insertSeparator(int position); int getCurrentPage(); int getTotalPages(); int getTotalPosts(); void updateCurrentPageNumber(); void syncPostsPerPage(); void disablePaginationButtons(); void resizePosts(QList postsToResize, bool resizeAll=false); void markPostsAsRead(); void updateFuzzyTimestamps(); bool commentingOnAnyPost(); void notifyBlockedUpdates(); QList getPostsInTimeline(); QFrame *getSeparatorFrame(); void showMessage(QString message); signals: void scrollTo(QAbstractSlider::SliderAction sliderAction); void timelineRendered(PumpController::requestTypes timelineType, int newPostCount, int highlightedPostsCount, int deletedPostsCount, int filteredPostsCount, int pendingForNextUpdate); void unreadPostsCountChanged(PumpController::requestTypes timelineType, int newPostCount, int highlightedCount, int fullTimelinePostCount); void commentingOnPost(QWidget *commenterWidget); public slots: void setTimeLineContents(QVariantList postList, QString previousLink, QString nextLink, int totalItems); void updatePostsFromMinorFeed(ASObject *object); void addLikesFromMinorFeed(QString objectId, QString objectType, QString actorId, QString actorName, QString actorUrl); void removeLikesFromMinorFeed(QString objectId, QString objectType, QString actorId); void addReplyFromMinorFeed(ASObject *object); void setPostsDeletedFromMinorFeed(ASObject *object); void setLikesInPost(QVariantList likesList, QString originatingPostURL); void setCommentsInPost(QVariantList commentsList, QString originatingPostURL); //void setSharesInPost(...) void goToFirstPage(); void goToPreviousPage(); void goToNextPage(); void goToSpecificPage(int pageNumber); void showPageSelector(); void scrollUp(); void scrollDown(); void scrollPageUp(); void scrollPageDown(); void scrollToTop(); void scrollToBottom(); void decreaseUnreadPostsCount(bool wasHighlighted); void updateAvatarFollowStates(); protected: private: QVBoxLayout *mainLayout; QVBoxLayout *postsLayout; QHBoxLayout *bottomLayout; PumpController *pController; GlobalObject *globalObj; FilterChecker *fChecker; QPushButton *getNewPendingButton; QLabel *infoLabel; QWidget *postsWidget; QFrame *separatorFrame; QPushButton *firstPageButton; QPushButton *currentPageButton; QPushButton *previousPageButton; QPushButton *nextPageButton; QString customUrl; QString previousPageLink; QString nextPageLink; int fullTimelinePostCount; int pendingToReceiveNextTime; bool firstLoad; bool gettingNew; bool wasOnFirstPage; int timelineOffset; int postsPerPage; int unreadPostsCount; int highlightedPostsCount; QAction *scrollUpAction; QAction *scrollDownAction; QAction *scrollPageUpAction; QAction *scrollPageDownAction; QAction *scrollTopAction; QAction *scrollBottomAction; QAction *previousPageAction; QAction *nextPageAction; PageSelector *pageSelector; PumpController::requestTypes timelineType; bool favoritesTimeline; QString previousNewestPostId; QList postsInTimeline; QStringList objectsIdList; }; #endif // TIMELINE_H dianara-v1.3.2/src/timeline.cpp000644 000764 000764 00000136314 12614427425 016014 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "timeline.h" TimeLine::TimeLine(PumpController::requestTypes timelineType, PumpController *pumpController, GlobalObject *globalObject, FilterChecker *filterChecker, QWidget *parent) : QWidget(parent) { this->timelineType = timelineType; this->pController = pumpController; this->globalObj = globalObject; this->fChecker = filterChecker; this->setMinimumSize(180, 180); // Ensure something's always visible this->favoritesTimeline = false; // Initialize // Simulated data for demo posts QVariantMap demoLocationData; demoLocationData.insert("displayName", "Demoville"); QVariantMap demoAuthorData; demoAuthorData.insert("displayName", "Demo User"); demoAuthorData.insert("id", "demo@somepump.example"); demoAuthorData.insert("url", "https://jancoding.wordpress.com/dianara"); demoAuthorData.insert("location", demoLocationData); demoAuthorData.insert("summary", "I am not a real user"); QVariantMap demoGeneratorData; demoGeneratorData.insert("displayName", "Dianara"); QVariantMap demoObjectData; demoObjectData.insert("objectType", "note"); demoObjectData.insert("id", "demo-post-id"); // Show date/time when Dianara v1.3.2 was released demoObjectData.insert("published", "2015-10-31T17:00:00Z"); QSettings settings; // FIXME: kinda tmp, until posts have "unread" status, etc. settings.beginGroup("TimelineStates"); // Demo post content depends on timeline type; also, restore some feed values switch (this->timelineType) { case PumpController::MainTimelineRequest: demoObjectData.insert("displayName", tr("Welcome to Dianara")); demoObjectData.insert("content", tr("Dianara is a Pump.io client.") + "
" + tr("If you don't have a Pump account yet, you can get one " "at the following address, for instance:") + "
" "
" "http://pump.io/tryit.html" "

" + tr("Press F1 if you want to open the Help window.") + "

" + tr("First, configure your account from the " "Settings - Account menu.") + " " + tr("After the process is done, your profile " "and timelines should update automatically.") + "

" + tr("Take a moment to look around the menus and " "the Configuration window.") + "

" + tr("You can also set your profile data and picture from " "the Settings - Edit Profile menu.") + "

" + tr("There are tooltips everywhere, so if you " "hover over a button or a text field with " "your mouse, you'll probably see some " "extra information.") + "

" + "" + tr("Dianara's blog") + "

" "" + tr("Pump.io User Guide") + "" + "

"); this->previousNewestPostId = settings.value("previousNewestPostIdMain").toString(); this->fullTimelinePostCount = settings.value("totalPostsMain").toInt(); break; case PumpController::DirectTimelineRequest: demoObjectData.insert("displayName", tr("Direct Messages Timeline")); demoObjectData.insert("content", tr("Here, you'll see posts " "specifically directed to you.") + "


"); this->previousNewestPostId = settings.value("previousNewestPostIdDirect").toString(); this->fullTimelinePostCount = settings.value("totalPostsDirect").toInt(); break; case PumpController::ActivityTimelineRequest: demoObjectData.insert("displayName", tr("Activity Timeline")); demoObjectData.insert("content", tr("You'll see your own posts here.") + "


"); this->previousNewestPostId = settings.value("previousNewestPostIdActivity").toString(); this->fullTimelinePostCount = settings.value("totalPostsActivity").toInt(); break; case PumpController::FavoritesTimelineRequest: demoObjectData.insert("displayName", tr("Favorites Timeline")); demoObjectData.insert("content", tr("Posts and comments you've liked.") + "


"); this->previousNewestPostId = settings.value("previousNewestPostIdFavorites").toString(); this->fullTimelinePostCount = settings.value("totalPostsFavorites").toInt(); this->favoritesTimeline = true; break; default: demoObjectData.insert("content", "

Empty timeline

"); } settings.endGroup(); QVariantMap demoPostData; demoPostData.insert("actor", demoAuthorData); demoPostData.insert("generator", demoGeneratorData); demoPostData.insert("object", demoObjectData); demoPostData.insert("id", "demo-activity-id"); this->firstLoad = true; this->gettingNew = true; // First time should be true this->unreadPostsCount = 0; this->timelineOffset = 0; this->wasOnFirstPage = true; this->pendingToReceiveNextTime = 0; this->syncPostsPerPage(); // Separator frame, to mark where new posts from the last batch end separatorFrame = new QFrame(this); separatorFrame->setFrameStyle(QFrame::HLine); separatorFrame->setMinimumHeight(28); separatorFrame->setContentsMargins(0, 8, 0, 8); separatorFrame->hide(); // Info label, shown when there are no posts, or to indicate loading and such infoLabel = new QLabel(this); infoLabel->setAlignment(Qt::AlignCenter); infoLabel->setWordWrap(true); infoLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); infoLabel->hide(); getNewPendingButton = new QPushButton(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), "*get more pending messages*", this); getNewPendingButton->setFlat(true); connect(getNewPendingButton, SIGNAL(clicked()), this, SLOT(goToFirstPage())); getNewPendingButton->hide(); // This will hold the posts and be hidden or disabled when needed postsWidget = new QWidget(this); postsWidget->setContentsMargins(0, 0, 0, 0); if (this->timelineType == PumpController::UserTimelineRequest) { postsWidget->hide(); // Will be shown when ready // Hiding it now ensures the infoLabel messages appear well centered } firstPageButton = new QPushButton(QIcon::fromTheme("go-first", QIcon(":/images/button-previous.png")), tr("Newest"), this); connect(firstPageButton, SIGNAL(clicked()), this, SLOT(goToFirstPage())); this->pageSelector = new PageSelector(this); connect(pageSelector, SIGNAL(pageJumpRequested(int)), this, SLOT(goToSpecificPage(int))); currentPageButton = new QPushButton(QIcon::fromTheme("go-next-view-page"), "1 / 1", // Correct value will be set on real update this); // currentPageButton->setFlat(true); // Not flat, for now, to make it discoverable currentPageButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); connect(currentPageButton, SIGNAL(clicked()), this, SLOT(showPageSelector())); previousPageButton = new QPushButton(QIcon::fromTheme("go-previous", QIcon(":/images/button-previous.png")), tr("Newer"), this); connect(previousPageButton, SIGNAL(clicked()), this, SLOT(goToPreviousPage())); nextPageButton = new QPushButton(QIcon::fromTheme("go-next", QIcon(":/images/button-next.png")), tr("Older"), this); connect(nextPageButton, SIGNAL(clicked()), this, SLOT(goToNextPage())); // Set reversed icons for bottom buttons if RTL language causes reversed layout if (qApp->layoutDirection() == Qt::RightToLeft) { this->firstPageButton->setIcon(QIcon::fromTheme("go-last", QIcon(":/images/button-next.png"))); this->previousPageButton->setIcon(QIcon::fromTheme("go-next", QIcon(":/images/button-next.png"))); this->nextPageButton->setIcon(QIcon::fromTheme("go-previous", QIcon(":/images/button-previous.png"))); } ///// Layout postsLayout = new QVBoxLayout(); postsLayout->setContentsMargins(0, 0, 0, 0); this->postsWidget->setLayout(postsLayout); // Setting alignment of this layout to AlignTop caused all posts to be // compressed at the top when there were a lot of them; removed bottomLayout = new QHBoxLayout(); bottomLayout->addSpacing(2); bottomLayout->addWidget(firstPageButton, 3); bottomLayout->addSpacing(2); bottomLayout->addStretch(1); bottomLayout->addSpacing(2); bottomLayout->addWidget(previousPageButton, 3); bottomLayout->addWidget(currentPageButton, 1); bottomLayout->addWidget(nextPageButton, 3); bottomLayout->addSpacing(2); mainLayout = new QVBoxLayout(); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addWidget(getNewPendingButton); mainLayout->addWidget(infoLabel, 2); mainLayout->addWidget(postsWidget, 1); mainLayout->addStretch(0); // Ensure buttons are always at the bottom mainLayout->addSpacing(2); // 2 pixel separation mainLayout->addLayout(bottomLayout, 0); this->setLayout(mainLayout); ////////////////////////////////////// QActions for better keyboard control // Single step scrollUpAction = new QAction(this); scrollUpAction->setShortcut(QKeySequence("Ctrl+Up")); connect(scrollUpAction, SIGNAL(triggered()), this, SLOT(scrollUp())); this->addAction(scrollUpAction); scrollDownAction = new QAction(this); scrollDownAction->setShortcut(QKeySequence("Ctrl+Down")); connect(scrollDownAction, SIGNAL(triggered()), this, SLOT(scrollDown())); this->addAction(scrollDownAction); // Pages scrollPageUpAction = new QAction(this); scrollPageUpAction->setShortcut(QKeySequence("Ctrl+PgUp")); connect(scrollPageUpAction, SIGNAL(triggered()), this, SLOT(scrollPageUp())); this->addAction(scrollPageUpAction); scrollPageDownAction = new QAction(this); scrollPageDownAction->setShortcut(QKeySequence("Ctrl+PgDown")); connect(scrollPageDownAction, SIGNAL(triggered()), this, SLOT(scrollPageDown())); this->addAction(scrollPageDownAction); // Top / Bottom scrollTopAction = new QAction(this); scrollTopAction->setShortcut(QKeySequence("Ctrl+Home")); connect(scrollTopAction, SIGNAL(triggered()), this, SLOT(scrollToTop())); this->addAction(scrollTopAction); scrollBottomAction = new QAction(this); scrollBottomAction->setShortcut(QKeySequence("Ctrl+End")); connect(scrollBottomAction, SIGNAL(triggered()), this, SLOT(scrollToBottom())); this->addAction(scrollBottomAction); // Previous/Next page in timeline previousPageAction = new QAction(this); previousPageAction->setShortcut(QKeySequence("Ctrl+Left")); connect(previousPageAction, SIGNAL(triggered()), previousPageButton, SLOT(click())); this->addAction(previousPageAction); nextPageAction = new QAction(this); nextPageAction->setShortcut(QKeySequence("Ctrl+Right")); connect(nextPageAction, SIGNAL(triggered()), nextPageButton, SLOT(click())); this->addAction(nextPageAction); // Add the default "demo" post if (timelineType != PumpController::UserTimelineRequest) { ASActivity *demoActivity = new ASActivity(demoPostData, this); Post *demoPost = new Post(demoActivity, false, // Not highlighted false, // Not standalone pController, globalObj, this); postsInTimeline.append(demoPost); postsLayout->addWidget(demoPost); } else { this->showMessage(tr("Requesting...")); } // Sync avatar's follow state for every post when there are changes in the Following list connect(pController, SIGNAL(followingListChanged()), this, SLOT(updateAvatarFollowStates())); // Disable buttons initially, until something is received this->disablePaginationButtons(); qDebug() << "TimeLine created"; } /* * Destructor stores timeline states in the settings * */ TimeLine::~TimeLine() { QSettings settings; settings.beginGroup("TimelineStates"); switch (timelineType) { case PumpController::MainTimelineRequest: settings.setValue("previousNewestPostIdMain", this->previousNewestPostId); settings.setValue("totalPostsMain", this->fullTimelinePostCount); break; case PumpController::DirectTimelineRequest: settings.setValue("previousNewestPostIdDirect", this->previousNewestPostId); settings.setValue("totalPostsDirect", this->fullTimelinePostCount); break; case PumpController::ActivityTimelineRequest: settings.setValue("previousNewestPostIdActivity", this->previousNewestPostId); settings.setValue("totalPostsActivity", this->fullTimelinePostCount); break; case PumpController::FavoritesTimelineRequest: settings.setValue("previousNewestPostIdFavorites", this->previousNewestPostId); settings.setValue("totalPostsFavorites", this->fullTimelinePostCount); break; default: qDebug() << "Timeline destructor: timelineType is invalid!"; } settings.endGroup(); qDebug() << "TimeLine destroyed; Type:" << this->timelineType; } void TimeLine::setCustomUrl(QString url) { this->customUrl = url; } /* * Remove all widgets (Post *) from the timeline * */ void TimeLine::clearTimeLineContents(bool showMessage) { foreach (Post *oldPost, postsInTimeline) { this->mainLayout->removeWidget(oldPost); delete oldPost; } this->postsInTimeline.clear(); this->objectsIdList.clear(); this->pendingToReceiveNextTime = 0; this->postsLayout->removeWidget(separatorFrame); separatorFrame->hide(); if (showMessage) { this->showMessage(tr("Loading...")); } qApp->processEvents(); // So GUI gets updated } /* * Remove oldest posts from current page, to avoid ever-increasing memory usage. * Called after updating the timeline, only when getting newer posts on the * first page. * * At the very least, keep as many posts as were received in last update. * */ void TimeLine::removeOldPosts(int minimumToKeep) { int maxPosts = qMax(this->postsPerPage * 2, // TMP FIXME minimumToKeep); if (postsInTimeline.count() <= maxPosts) { // Not too many posts yet, so do nothing return; } int postCounter = 0; int deletedCounter = 0; // tmp, TESTS (EXTRALOGGING) foreach (Post *post, postsInTimeline) { if (postCounter >= maxPosts) { if (!post->isNew() // Don't remove if it's unread && !post->isBeingCommented()) // or currently being commented on { this->postsLayout->removeWidget(post); this->postsInTimeline.removeOne(post); delete post; ++deletedCounter; // tmp, TESTS (EXTRALOGGING) } } ++postCounter; } // Update "next" link manually, based on the last post present in the page QByteArray lastPostId = postsInTimeline.last()->getActivityId().toLocal8Bit(); lastPostId = lastPostId.toPercentEncoding(); // Needs to be percent-encoded this->nextPageLink = this->pController->getFeedApiUrl(this->timelineType) + "?before=" + lastPostId; #ifdef EXTRALOGGING // tmp, TESTS - debugging via log this->globalObj->logMessage(QString("##### %1 POSTS IN '%2' AFTER REMOVAL; " "%3 CAME IN, %4 WERE DELETED - " "NEXT PAGE: %5") .arg(postsInTimeline.count()) .arg(PumpController::getFeedNameAndPath(this->timelineType).first()) .arg(minimumToKeep) .arg(deletedCounter) .arg(this->nextPageLink)); #endif } void TimeLine::insertSeparator(int position) { this->postsLayout->insertWidget(position, this->separatorFrame); this->separatorFrame->show(); } int TimeLine::getCurrentPage() { if (this->postsPerPage == 0) { this->postsPerPage = 1; } return (this->timelineOffset / this->postsPerPage) + 1; } int TimeLine::getTotalPages() { if (this->postsPerPage == 0) { this->postsPerPage = 1; } int totalPages = qCeil(this->fullTimelinePostCount / (float)this->postsPerPage); return qMax(totalPages, 1); // 1 is the minimum } int TimeLine::getTotalPosts() { return this->fullTimelinePostCount; } /* * Update the button at the bottom of the page, indicating current "page" * */ void TimeLine::updateCurrentPageNumber() { int currentPage = this->getCurrentPage(); int totalPages = this->getTotalPages(); QString currentPageString = QLocale::system().toString(currentPage); QString totalPagesString = QLocale::system().toString(totalPages); QString totalPostsString = QLocale::system() .toString(this->fullTimelinePostCount); this->currentPageButton->setText(QString("%1 / %2") .arg(currentPageString) .arg(totalPagesString)); // The shortcut needs to be set each time the text is changed currentPageButton->setShortcut(QKeySequence("Ctrl+G")); this->currentPageButton->setToolTip(tr("Page %1 of %2.") .arg(currentPageString) .arg(totalPagesString) + "
" + tr("Showing %1 posts per page.") .arg(this->postsPerPage) + "
" + tr("%1 posts in total.") .arg(totalPostsString) + "
" "" + tr("Click here or press Control+G to " "jump to a specific page") + ""); this->previousPageButton->setDisabled(currentPage == 1); // Disabled on 1st page this->nextPageButton->setDisabled(currentPage == totalPages); // Disabled on last page } void TimeLine::syncPostsPerPage() { if (this->timelineType == PumpController::MainTimelineRequest || this->timelineType == PumpController::UserTimelineRequest) { this->postsPerPage = this->globalObj->getPostsPerPageMain(); } else { this->postsPerPage = this->globalObj->getPostsPerPageOther(); } } void TimeLine::disablePaginationButtons() { this->firstPageButton->setDisabled(true); this->previousPageButton->setDisabled(true); this->currentPageButton->setDisabled(true); this->nextPageButton->setDisabled(true); this->getNewPendingButton->setDisabled(true); } /* * Resize all posts in timeline * */ void TimeLine::resizePosts(QList postsToResize, bool resizeAll) { if (resizeAll) { postsToResize = this->postsInTimeline; } foreach (Post *post, postsToResize) { post->resetResizesCount(); // Call setPostContents() and setPostHeight() // New method, disabled for 1.3.1; has some drawbacks //post->onResizeOrShow(); /* -- Old method, forcing a resize */ post->resize(post->width() - 1, post->height() - 1); /* re-enabled for 1.3.1 */ } } void TimeLine::markPostsAsRead() { foreach (Post *post, postsInTimeline) { // Mark post as read without informing the timeline post->setPostAsRead(false); } unreadPostsCount = 0; highlightedPostsCount = 0; emit unreadPostsCountChanged(this->timelineType, this->unreadPostsCount, this->highlightedPostsCount, this->fullTimelinePostCount); } void TimeLine::updateFuzzyTimestamps() { foreach (Post *post, postsInTimeline) { post->setFuzzyTimestamps(); } } bool TimeLine::commentingOnAnyPost() { foreach (Post *post, postsInTimeline) { if (post->isBeingCommented()) { return true; } } return false; } void TimeLine::notifyBlockedUpdates() { QString tlName = PumpController::getFeedNameAndPath(timelineType).first(); this->globalObj->setStatusMessage(tr("'%1' cannot be updated " "because a comment is currently " "being composed.", "%1 = feed's name").arg(tlName)); } /* * Return list of pointers to Post() objects currently in the timeline * */ QList TimeLine::getPostsInTimeline() { return this->postsInTimeline; } QFrame *TimeLine::getSeparatorFrame() { return this->separatorFrame; } void TimeLine::showMessage(QString message) { this->infoLabel->setText("" + message + ""); this->infoLabel->show(); } /*****************************************************************************/ /*****************************************************************************/ /********************************** SLOTS ************************************/ /*****************************************************************************/ /*****************************************************************************/ void TimeLine::setTimeLineContents(QVariantList postList, QString previousLink, QString nextLink, int totalItems) { qDebug() << "TimeLine::setTimeLineContents()"; int postListSize = postList.size(); // Disable to avoid clicks to posts (which would mark them as read) if (postListSize > 0) // until fully updated { this->setDisabled(true); } // Remove all previous posts in timeline, when switching pages if (firstLoad || !wasOnFirstPage || favoritesTimeline || timelineOffset > 0) { // Hide the posts while TL reloads; helps performance a lot this->postsWidget->hide(); qDebug() << "Removing previous posts from timeline"; this->clearTimeLineContents(); this->unreadPostsCount = 0; this->highlightedPostsCount = 0; // Ask mainWindow to scroll the QScrollArea containing the timeline to the top // emit scrollTo(QAbstractSlider::SliderToMinimum); ////////// TMP FIXME: don't scroll to top; make it optional this->previousPageLink = previousLink; this->nextPageLink = nextLink; qDebug() << "Prev/Next links:" << previousPageLink << nextPageLink; } else { if (!previousLink.isEmpty()) { this->previousPageLink = previousLink; // Just the previousLink; don't store nextLink, keep the old one } } int totalPostDifference = totalItems - this->fullTimelinePostCount; this->fullTimelinePostCount = totalItems; // Check how many more posts need to be received, if more than max are pending pendingToReceiveNextTime += totalPostDifference; pendingToReceiveNextTime -= postListSize; if (pendingToReceiveNextTime > 0) { if (firstLoad) { // The difference in pending posts is in the older pages, so doesn't count this->pendingToReceiveNextTime = 0; // FIXME 1.3.x: On first load, should display the "pending" number // at the bottom or at the "older" button } else { // Button at the top to fetch the pending messages, even more new stuff this->getNewPendingButton->setText(tr("%1 more posts pending for " "next update.") .arg(pendingToReceiveNextTime) + " " // 3 spaces, then a watch + QString::fromUtf8("\342\214\232") .arg(pendingToReceiveNextTime) + "\n" + tr("Click here to receive " "them now.")); this->getNewPendingButton->setEnabled(true); this->getNewPendingButton->show(); } } else { this->pendingToReceiveNextTime = 0; // In case it was less than 0 this->getNewPendingButton->hide(); } // Remove the current separator line this->postsLayout->removeWidget(separatorFrame); separatorFrame->hide(); ////////////////////////////////////// Start adding content to the timeline int newPostCount = 0; int newHighlightedPostsCount = 0; int newDeletedPostsCount = 0; int newFilteredPostsCount = 0; bool allNewPostsCounted = false; int insertedPosts = 0; bool needToInsertSeparator = false; // Here we'll store the postID for the first (newest) post in the timeline QString newestPostId; // (actually activity ID) // With it, we can know how many new posts (if any) we receive next time QList postsInsertedThisTime; // Fill timeline with new contents foreach (QVariant singlePost, postList) { if (singlePost.type() == QVariant::Map) { bool postIsNew = false; QVariantMap activityMap; // Since "Favorites" is a collection of objects, not activities, // we need to put "Favorites" posts into fake activities if (!favoritesTimeline) { // Data is already an activity activityMap = singlePost.toMap(); } else { // Put object into the empty/fake VariantMap for the activity activityMap.insert("object", singlePost.toMap()); activityMap.insert("actor", singlePost.toMap() .value("author").toMap()); activityMap.insert("id", singlePost.toMap() .value("id").toString()); } ASActivity *activity = new ASActivity(activityMap, this); // See if it's deleted QString postDeletedTime = activity->object()->getDeletedTime(); // See if we have to filter it out (or highlight it) int filtered = this->fChecker->validateActivity(activity); // See if we hide the post if it's already visible in the timeline bool postIsDuplicated = false; if (globalObj->getHideDuplicates()) // Depending on the setting { if (this->objectsIdList.contains(activity->object()->getId())) { postIsDuplicated = true; } } if (newestPostId.isEmpty()) // only first time, for newest post { if (gettingNew) { newestPostId = activity->getId(); } else { newestPostId = this->previousNewestPostId; allNewPostsCounted = true; } } if (!allNewPostsCounted) { if (activity->getId() == this->previousNewestPostId) { allNewPostsCounted = true; if (newPostCount > 0) { needToInsertSeparator = true; } } else { // If post is NOT deleted or filtered, not ours, and // this is not the Favorites timeline, add it to the count if (postDeletedTime.isEmpty() && filtered != FilterChecker::FilterOut && !postIsDuplicated && activity->author()->getId() != pController->currentUserId() && activity->object()->author()->getId() != pController->currentUserId() && !favoritesTimeline && this->timelineType != PumpController::UserTimelineRequest) { ++newPostCount; // Mark current post as new postIsNew = true; } else { if (!postDeletedTime.isEmpty()) { ++newDeletedPostsCount; } else if (filtered == FilterChecker::FilterOut || postIsDuplicated) { ++newFilteredPostsCount; } } } } bool highlightedByFilter = false; if (filtered == FilterChecker::Highlight) { highlightedByFilter = true; } Post *newPost = new Post(activity, highlightedByFilter, false, // NOT standalone pController, globalObj, this); if (postIsNew) { newPost->setPostAsNew(); connect(newPost, SIGNAL(postRead(bool)), this, SLOT(decreaseUnreadPostsCount(bool))); if (newPost->getHighlightType() != Post::NoHighlight) { ++newHighlightedPostsCount; } } if (needToInsertSeparator) // ------- { this->insertSeparator(insertedPosts); ++insertedPosts; needToInsertSeparator = false; } this->objectsIdList.append(newPost->getObjectId()); postsInsertedThisTime.append(newPost); this->postsLayout->insertWidget(insertedPosts, newPost); this->postsInTimeline.insert(insertedPosts, newPost); ++insertedPosts; // FIXME: this signal should go directly via GlobalObject instead connect(newPost, SIGNAL(commentingOnPost(QWidget*)), this, SIGNAL(commentingOnPost(QWidget*))); // If post has been filtered out or hidden because it's a duplicate if (filtered == FilterChecker::FilterOut || postIsDuplicated) { newPost->hide(); // For now; maybe make it so that it can be clicked to show - FIXME qDebug() << "Post filtered out or hidden because it's a duplicate\n" << "Filter action:" << filtered << "(0=filter out; 1=highlight, 999=no filtering)\n" << "Duplicated:" << postIsDuplicated; } } else // singlePost.type() is not a QVariant::Map { qDebug() << "Expected a Map, got something else"; qDebug() << postList; } } // end foreach qApp->processEvents(); // pre-resize posts this->firstLoad = false; // If there were new posts, and separator not already added, add it: ----- if (newPostCount > 0 && this->separatorFrame->isHidden()) { this->insertSeparator(insertedPosts); } if (!newestPostId.isEmpty()) { this->previousNewestPostId = newestPostId; } this->unreadPostsCount += newPostCount; this->highlightedPostsCount += newHighlightedPostsCount; qDebug() << "-----------\nNew posts:" << newPostCount << "\nActual total new from previous update:" << totalPostDifference << "\nNewest post ID:" << previousNewestPostId << "\nNew highlighted:" << newHighlightedPostsCount << "\nNew deleted: " << newDeletedPostsCount << "\nNew filtered out: " << newFilteredPostsCount << "\nTotal posts:" << fullTimelinePostCount << "\nTotal currently loaded posts:" << this->postsInTimeline.size(); if (postListSize > 0) { // Resize the posts, but only the ones added in this update this->resizePosts(postsInsertedThisTime); } if (gettingNew) { emit timelineRendered(this->timelineType, newPostCount, newHighlightedPostsCount, newDeletedPostsCount, newFilteredPostsCount, pendingToReceiveNextTime); emit unreadPostsCountChanged(this->timelineType, unreadPostsCount, highlightedPostsCount, fullTimelinePostCount); // Clean up, keeping at least the posts that were just received if (postListSize > 0) // but only if there was _something_ { this->removeOldPosts(postListSize); } } else { emit timelineRendered(this->timelineType, postListSize, -1, newDeletedPostsCount, newFilteredPostsCount, -1); emit unreadPostsCountChanged(this->timelineType, 0, 0, fullTimelinePostCount); } if (postsInTimeline.length() == 0) { this->showMessage(tr("There are no posts")); this->postsWidget->hide(); } else { this->infoLabel->hide(); this->postsWidget->show(); // Show posts again } // Enable timeline again, since everything is added and drawn this->setEnabled(true); this->firstPageButton->setEnabled(true); this->currentPageButton->setEnabled(true); this->updateCurrentPageNumber(); // Even when not needed, will re-enable some buttons qDebug() << "setTimeLineContents() /END"; } /* * Update data in all currently-visible posts matching the object ID sent * by the minor feed * */ void TimeLine::updatePostsFromMinorFeed(ASObject *object) { /* FIXME: This should handle cases where a comment might be visible as a * post in the timeline (shared) _and_ be a comment in a visible post * * Also, something could be a note, therefore visible in the timeline, * but also be in reply to something else. * */ if (object->getInReplyToId().isEmpty()) // Parent object { foreach (Post *post, postsInTimeline) { if (post->getObjectId() == object->getId()) { post->updateDataFromObject(object); } } } else // Reply to something { foreach (Post *post, postsInTimeline) { if (post->getObjectId() == object->getInReplyToId()) { post->updateCommentFromObject(object); } } } } void TimeLine::addLikesFromMinorFeed(QString objectId, QString objectType, QString actorId, QString actorName, QString actorUrl) { // FIXME: handle updating likes in comments foreach (Post *post, postsInTimeline) { if (post->getObjectId() == objectId) { post->appendLike(actorId, actorName, actorUrl); } } } void TimeLine::removeLikesFromMinorFeed(QString objectId, QString objectType, QString actorId) { // FIXME: handle updating likes in comments foreach (Post *post, postsInTimeline) { if (post->getObjectId() == objectId) { post->removeLike(actorId); } } } /* * Add one single comment read from a minor feed, to the * corresponding parent post in this timeline * */ void TimeLine::addReplyFromMinorFeed(ASObject *object) { QString parentPostId = object->getInReplyToId(); foreach (Post *post, postsInTimeline) { if (post->getObjectId() == parentPostId) { post->appendComment(object); } } } void TimeLine::setPostsDeletedFromMinorFeed(ASObject *object) { foreach (Post *post, postsInTimeline) { if (post->getObjectId() == object->getId()) { post->setPostAsRead(true); // Just in case, and notifying timeline post->setPostDeleted(object->getDeletedOnString()); } } // If the object has a parent, find it in the comments if (!object->getInReplyToId().isEmpty()) { foreach (Post *post, postsInTimeline) { if (post->getObjectId() == object->getInReplyToId()) { post->setCommentDeletedFromObject(object); } } } } /* * Add the full list of likes to a post * */ void TimeLine::setLikesInPost(QVariantList likesList, QString originatingPostURL) { //qDebug() << "TimeLine::setLikesInPost()"; QString originatingPostCleanUrl = originatingPostURL.split("?").first(); //qDebug() << "Originating post URL:" << originatingPostCleanUrl; // Look for the originating Post() object qDebug() << "Looking for the originating Post() object"; foreach (Post *post, postsInTimeline) { if (post->likesUrl() == originatingPostCleanUrl) { qDebug() << "Found originating Post; setting likes on it..."; post->setLikes(likesList); /* Don't break, so likes get set in other * visible copies of the post too */ } } } /* * Add the full list of comments to a post * */ void TimeLine::setCommentsInPost(QVariantList commentsList, QString originatingPostURL) { qDebug() << "TimeLine::setCommentsInPost()"; QString originatingPostCleanUrl = originatingPostURL.split("?").first(); //qDebug() << "Originating post URL:" << originatingPostCleanUrl; // Look for the originating Post() object qDebug() << "Looking for the originating Post() object"; foreach (Post *post, postsInTimeline) { if (post->commentsURL() == originatingPostCleanUrl) { qDebug() << "Found originating Post; setting comments on it..."; post->setComments(commentsList); // break; /* Don't break, so comments get set in copies of the post too, like if JohnDoe posted something and JaneDoe shared it soon after, so both the original post and its shared copy are visible in the timeline. */ } } } void TimeLine::goToFirstPage() { qDebug() << "TimeLine::goToFirstPage()"; if (this->commentingOnAnyPost()) { // Update is blocked because a post is being commented this->notifyBlockedUpdates(); return; } this->syncPostsPerPage(); this->gettingNew = true; if (timelineOffset == 0) // On page 1 { this->wasOnFirstPage = true; } else { this->wasOnFirstPage = false; this->previousPageLink.clear(); // Full reload of newest stuff this->timelineOffset = 0; this->setDisabled(true); // Disable soon, to avoid wrong clicks } // Disable pagination buttons to avoid double-clicking problems, in case // the whole timeline isn't disabled (whenever we're in the first page) this->disablePaginationButtons(); // The ones that make sense will be re-enabled later /* * If this is the favorites timeline, previousPageLink will be empty, * which means the first posts at offset 0 will be loaded anyway * */ pController->getFeed(this->timelineType, this->postsPerPage, this->previousPageLink); } void TimeLine::goToPreviousPage() { qDebug() << "TimeLine::goToPreviousPage()"; if (this->commentingOnAnyPost()) { this->notifyBlockedUpdates(); return; } this->syncPostsPerPage(); this->gettingNew = false; this->setDisabled(true); // Disable soon, to avoid wrong clicks this->timelineOffset -= this->postsPerPage; if (timelineOffset < 0) { timelineOffset = 0; } if (!favoritesTimeline) // Not favorites, use PreviousLink { pController->getFeed(this->timelineType, this->postsPerPage, this->previousPageLink); } else { pController->getFeed(this->timelineType, this->postsPerPage, "", this->timelineOffset); } } void TimeLine::goToNextPage() { qDebug() << "TimeLine::goToNextPage()"; if (this->commentingOnAnyPost()) { this->notifyBlockedUpdates(); return; } this->syncPostsPerPage(); this->gettingNew = false; this->setDisabled(true); // Disable soon, to avoid wrong clicks this->timelineOffset += this->postsPerPage; if (!favoritesTimeline) // Not favorites, use NextLink { pController->getFeed(this->timelineType, this->postsPerPage, this->nextPageLink); } else // Use offset { pController->getFeed(this->timelineType, this->postsPerPage, "", this->timelineOffset); } } void TimeLine::goToSpecificPage(int pageNumber) { qDebug() << "TimeLine::goToSpecificPage(): " << pageNumber; if (this->commentingOnAnyPost()) { this->notifyBlockedUpdates(); return; } this->syncPostsPerPage(); this->gettingNew = false; this->setDisabled(true); // Disable soon, to avoid wrong clicks this->timelineOffset = (pageNumber - 1) * this->postsPerPage; /* Workaround for Pump.io core bug: * * Ensure that timelineOffset is NOT greater * than fullTimelinePostCount - postsPerPage * * Otherwise, when requesting the last page, the server might return * ALL posts, even beyond API limits, instead of the last (oldest) few * posts. * * https://github.com/e14n/pump.io/issues/1087 * */ if (this->timelineType == PumpController::UserTimelineRequest) { int maxTimelineOffset = this->fullTimelinePostCount - this->postsPerPage; this->timelineOffset = qMin(this->timelineOffset, maxTimelineOffset); } pController->getFeed(this->timelineType, this->postsPerPage, this->customUrl, // No prev/next links, use possible customUrl or empty this->timelineOffset); // And use offset instead } void TimeLine::showPageSelector() { this->pageSelector->showForPage(this->getCurrentPage(), this->getTotalPages()); } void TimeLine::scrollUp() { emit scrollTo(QAbstractSlider::SliderSingleStepSub); } void TimeLine::scrollDown() { emit scrollTo(QAbstractSlider::SliderSingleStepAdd); } void TimeLine::scrollPageUp() { emit scrollTo(QAbstractSlider::SliderPageStepSub); } void TimeLine::scrollPageDown() { emit scrollTo(QAbstractSlider::SliderPageStepAdd); } void TimeLine::scrollToTop() { emit scrollTo(QAbstractSlider::SliderToMinimum); } void TimeLine::scrollToBottom() { emit scrollTo(QAbstractSlider::SliderToMaximum); } /* * Decrease internal counter of unread posts (by 1), and inform * the parent window, so it can update its tab titles * */ void TimeLine::decreaseUnreadPostsCount(bool wasHighlighted) { --unreadPostsCount; if (wasHighlighted) { --highlightedPostsCount; } emit unreadPostsCountChanged(this->timelineType, this->unreadPostsCount, this->highlightedPostsCount, this->fullTimelinePostCount); } void TimeLine::updateAvatarFollowStates() { foreach (Post *post, postsInTimeline) { post->syncAvatarFollowState(); } } dianara-v1.3.2/src/fontpicker.cpp000644 000764 000764 00000006004 12601647777 016354 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "fontpicker.h" FontPicker::FontPicker(QString description, QString initialFontString, QWidget *parent) : QWidget(parent) { QFont initialFont; initialFont.fromString(initialFontString); if (initialFont.family().isEmpty()) // Invalid, so use standard font { this->currentFont = QFont(); } else // Valid, so use it { this->currentFont = initialFont; } this->descriptionLabel = new QLabel(description, this); descriptionLabel->setWordWrap(true); this->sampleLineEdit = new QLineEdit("AaBbCcDdEeFf", this); sampleLineEdit->setReadOnly(true); updateFontSample(); this->button = new QPushButton(QIcon::fromTheme("list-add-font", QIcon(":/images/list-add.png")), tr("Change"), this); connect(button, SIGNAL(clicked()), this, SLOT(selectFont())); this->layout = new QHBoxLayout(); layout->addWidget(descriptionLabel, 2); layout->addWidget(sampleLineEdit, 5); layout->addWidget(button, 1); this->setLayout(layout); qDebug() << "FontPicker created"; } FontPicker::~FontPicker() { qDebug() << "FontPicker destroyed"; } void FontPicker::updateFontSample() { this->sampleLineEdit->setFont(currentFont); this->sampleLineEdit->setText(currentFont.toString()); this->sampleLineEdit->setCursorPosition(0); } QString FontPicker::getFontInfo() { return this->currentFont.toString(); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////// SLOTS ///////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void FontPicker::selectFont() { bool fontOk = false; QFont newFont = QFontDialog::getFont(&fontOk, this->currentFont, this, tr("Choose a font")); if (fontOk) { this->currentFont = newFont; updateFontSample(); } } dianara-v1.3.2/src/fontpicker.h000664 000764 000764 00000003012 12451104034 015770 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef FONTPICKER_H #define FONTPICKER_H #include #include #include #include #include #include #include class FontPicker : public QWidget { Q_OBJECT public: explicit FontPicker(QString description, QString initialFontString, QWidget *parent = 0); ~FontPicker(); void updateFontSample(); QString getFontInfo(); signals: public slots: void selectFont(); private: QHBoxLayout *layout; QLabel *descriptionLabel; QLineEdit *sampleLineEdit; QPushButton *button; QFont currentFont; }; #endif // FONTPICKER_H dianara-v1.3.2/src/publisher.cpp000644 000764 000764 00000151752 12614425367 016211 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "publisher.h" Publisher::Publisher(PumpController *pumpController, GlobalObject *globalObject, QWidget *parent) : QWidget(parent) { this->pController = pumpController; connect(pController, SIGNAL(postPublished()), this, SLOT(onPublishingOk())); connect(pController, SIGNAL(postPublishingFailed()), this, SLOT(onPublishingFailed())); // After receiving the list of lists, update the "Lists" submenus connect(pController, SIGNAL(listsListReceived(QVariantList)), this, SLOT(updateListsMenus(QVariantList))); this->globalObj = globalObject; connect(globalObj, SIGNAL(messagingModeRequested(QString,QString,QString)), this, SLOT(startMessageForContact(QString,QString,QString))); connect(globalObj, SIGNAL(postEditRequested(QString,QString,QString,QString)), this, SLOT(setEditingMode(QString,QString,QString,QString))); this->postType = "note"; this->editingMode = false; // False unless set from setEditingMode // after clicking "Edit" in a post this->defaultPublicPosting = false; // initialize this->showCharacterCounter = false; this->setFocusPolicy(Qt::StrongFocus); // To keep the publisher from getting focus by accident this->audienceSelectorTo = new AudienceSelector(pController, "to", this); connect(audienceSelectorTo, SIGNAL(audienceSelected(QString,QStringList,QStringList)), this, SLOT(updateToCcFields(QString,QStringList,QStringList))); this->audienceSelectorCC = new AudienceSelector(pController, "cc", this); connect(audienceSelectorCC, SIGNAL(audienceSelected(QString,QStringList,QStringList)), this, SLOT(updateToCcFields(QString,QStringList,QStringList))); QString titleTooltip = "" + tr("Setting a title helps make the " "Meanwhile feed more informative"); QFont titleFont; titleFont.setPointSize(titleFont.pointSize() + 1); titleFont.setBold(true); titleLabel = new QLabel(tr("Title") + ":"); titleLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum); titleLabel->setFont(titleFont); titleLabel->setToolTip(titleTooltip); titleLineEdit = new QLineEdit(); titleLineEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); titleLineEdit->setPlaceholderText(tr("Add a brief title for the post here " "(recommended)")); titleLineEdit->setFont(titleFont); titleLineEdit->setToolTip(titleTooltip); pictureLabel = new QLabel(); pictureLabel->setAlignment(Qt::AlignCenter); pictureLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); pictureLabel->hide(); mediaInfoLabel = new QLabel(); mediaInfoLabel->setWordWrap(true); mediaInfoLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); selectMediaButton = new QPushButton(); selectMediaButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); connect(selectMediaButton, SIGNAL(clicked()), this, SLOT(findMediaFile())); selectMediaButton->hide(); uploadProgressBar = new QProgressBar(this); removeMediaButton = new QPushButton(QIcon::fromTheme("edit-delete", QIcon(":/images/button-delete.png")), tr("Remove")); removeMediaButton->setToolTip("" + tr("Cancel the attachment, and go " "back to a regular note")); removeMediaButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); connect(removeMediaButton, SIGNAL(clicked()), this, SLOT(cancelMediaMode())); removeMediaButton->hide(); // Set default pixmap and "media not set" message this->setEmptyMediaData(); lastUsedDirectory = QDir::homePath(); // Composer composerBox = new Composer(this->globalObj, true, // forPublisher = true this); composerBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); connect(composerBox, SIGNAL(focusReceived()), this, SLOT(setFullMode())); connect(composerBox, SIGNAL(editingFinished()), this, SLOT(sendPost())); connect(composerBox, SIGNAL(editingCancelled()), this, SLOT(setMinimumMode())); connect(composerBox, SIGNAL(nickInserted(QString,QString,QString)), this, SLOT(addNickToRecipients(QString,QString,QString))); connect(composerBox, SIGNAL(textChanged()), this, SLOT(updateCharacterCounter())); // Pressing Enter in title goes to message body connect(titleLineEdit, SIGNAL(returnPressed()), composerBox, SLOT(setFocus())); // Likewise, pressing UP at the start of the body goes to title connect(composerBox, SIGNAL(focusTitleRequested()), titleLineEdit, SLOT(setFocus())); // Add formatting button exported from Composer this->toolsButton = composerBox->getToolsButton(); // To... menu toPublicAction = new QAction(tr("Public"), this); toPublicAction->setCheckable(true); connect(toPublicAction, SIGNAL(toggled(bool)), this, SLOT(setToPublic(bool))); toFollowersAction = new QAction(tr("Followers"), this); toFollowersAction->setCheckable(true); connect(toFollowersAction, SIGNAL(toggled(bool)), this, SLOT(setToFollowers(bool))); toSelectorListsMenu = new QMenu(tr("Lists"), this); toSelectorListsMenu->setDisabled(true); // Disabled until lists are received, if any connect(toSelectorListsMenu, SIGNAL(triggered(QAction*)), this, SLOT(updateToListsFields(QAction*))); toSelectorMenu = new QMenu("to-menu", this); toSelectorMenu->addAction(toPublicAction); toSelectorMenu->addAction(toFollowersAction); toSelectorMenu->addMenu(toSelectorListsMenu); toSelectorMenu->addSeparator(); toSelectorMenu->addAction(tr("People..."), audienceSelectorTo, SLOT(show())); toSelectorButton = new QPushButton(QIcon::fromTheme("system-users", QIcon(":/images/no-avatar.png")), tr("To..."), this); toSelectorButton->setToolTip("" + tr("Select who will see this post")); toSelectorButton->setMenu(toSelectorMenu); // Cc... menu ccPublicAction = new QAction(tr("Public"), this); ccPublicAction->setCheckable(true); connect(ccPublicAction, SIGNAL(toggled(bool)), this, SLOT(setCCPublic(bool))); ccFollowersAction = new QAction(tr("Followers"), this); ccFollowersAction->setCheckable(true); connect(ccFollowersAction, SIGNAL(toggled(bool)), this, SLOT(setCCFollowers(bool))); ccSelectorListsMenu = new QMenu(tr("Lists"), this); ccSelectorListsMenu->setDisabled(true); // Disabled until lists are received, if any connect(ccSelectorListsMenu, SIGNAL(triggered(QAction*)), this, SLOT(updateCcListsFields(QAction*))); ccSelectorMenu = new QMenu("cc-menu", this); ccSelectorMenu->addAction(ccPublicAction); ccSelectorMenu->addAction(ccFollowersAction); ccSelectorMenu->addMenu(ccSelectorListsMenu); ccSelectorMenu->addSeparator(); ccSelectorMenu->addAction(tr("People..."), audienceSelectorCC, SLOT(show())); ccSelectorButton = new QPushButton(QIcon::fromTheme("system-users", QIcon(":/images/no-avatar.png")), tr("Cc..."), this); ccSelectorButton->setToolTip("" + tr("Select who will get a copy of this post")); ccSelectorButton->setMenu(ccSelectorMenu); QFont audienceLabelsFont; // "To" column will be normal, "Cc" will be italic audienceLabelsFont.setPointSize(audienceLabelsFont.pointSize() - 1); // These will hold the names of the *people* selected for the To or Cc fields, if any toAudienceLabel = new QLabel(this); toAudienceLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); toAudienceLabel->setFont(audienceLabelsFont); // Normal toAudienceLabel->setWordWrap(true); toAudienceLabel->setOpenExternalLinks(true); connect(toAudienceLabel, SIGNAL(linkHovered(QString)), this, SLOT(showHighlightedUrl(QString))); ccAudienceLabel = new QLabel(this); ccAudienceLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); audienceLabelsFont.setItalic(true); ccAudienceLabel->setFont(audienceLabelsFont); // Italic ccAudienceLabel->setWordWrap(true); ccAudienceLabel->setOpenExternalLinks(true); connect(ccAudienceLabel, SIGNAL(linkHovered(QString)), this, SLOT(showHighlightedUrl(QString))); // And these will show "public" or "followers" current selection (in bold) toPublicFollowersLabel = new QLabel(); toPublicFollowersLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); audienceLabelsFont.setBold(true); audienceLabelsFont.setItalic(false); toPublicFollowersLabel->setFont(audienceLabelsFont); // Bold ccPublicFollowersLabel = new QLabel(); ccPublicFollowersLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); audienceLabelsFont.setItalic(true); ccPublicFollowersLabel->setFont(audienceLabelsFont); // Bold + italic // Menu to set the picture/audio/video modes, under an "Add..." button addMediaImageAction = new QAction(QIcon::fromTheme("camera-photo", QIcon(":/images/attached-image.png")), tr("Picture"), this); connect(addMediaImageAction, SIGNAL(triggered()), this, SLOT(setPictureMode())); addMediaAudioAction = new QAction(QIcon::fromTheme("audio-input-microphone", QIcon(":/images/attached-audio.png")), tr("Audio"), this); connect(addMediaAudioAction, SIGNAL(triggered()), this, SLOT(setAudioMode())); addMediaVideoAction = new QAction(QIcon::fromTheme("camera-web", QIcon(":/images/attached-video.png")), tr("Video"), this); connect(addMediaVideoAction, SIGNAL(triggered()), this, SLOT(setVideoMode())); addMediaFileAction = new QAction(QIcon::fromTheme("application-octet-stream", QIcon(":/images/attached-file.png")), tr("Other", "as in other kinds of files"), this); connect(addMediaFileAction, SIGNAL(triggered()), this, SLOT(setFileMode())); // The menu itself addMediaMenu = new QMenu("add-media-menu", this); addMediaMenu->addAction(addMediaImageAction); addMediaMenu->addAction(addMediaAudioAction); addMediaMenu->addAction(addMediaVideoAction); addMediaMenu->addAction(addMediaFileAction); // The "add..." button holding the menu addMediaButton = new QPushButton(QIcon::fromTheme("mail-attachment", QIcon(":/images/list-add.png")), tr("Ad&d...")); addMediaButton->setToolTip("" + tr("Upload media, like pictures or videos")); addMediaButton->setMenu(addMediaMenu); // Character counter (optional) charCounterLabel = new QLabel(this); charCounterLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); charCounterLabel->setText("0"); // Info label for "sending" and other status information statusInfoLabel = new QLabel(); statusInfoLabel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); statusInfoLabel->setAlignment(Qt::AlignCenter); statusInfoLabel->setWordWrap(true); audienceLabelsFont.setBold(false); audienceLabelsFont.setItalic(false); statusInfoLabel->setFont(audienceLabelsFont); // To send the post postButton = new QPushButton(QIcon::fromTheme("mail-send", QIcon(":/images/button-post.png")), tr("Post", "verb")); postButton->setToolTip("" + tr("Hit Control+Enter to post with the keyboard")); connect(postButton, SIGNAL(clicked()), this, SLOT(sendPost())); cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("Cancel")); cancelButton->setToolTip("" + tr("Cancel the post")); connect(cancelButton, SIGNAL(clicked()), composerBox, SLOT(cancelPost())); // Now the layout, starting with the Title field and "picture mode" stuff titleLayout = new QHBoxLayout(); titleLayout->addWidget(titleLabel); titleLayout->addWidget(titleLineEdit); titleLayout->addSpacing(12); titleLayout->addWidget(toolsButton); mainLayout = new QGridLayout(); mainLayout->setVerticalSpacing(1); mainLayout->setContentsMargins(1, 1, 1, 1); mainLayout->addLayout(titleLayout, 0, 0, 1, 8, Qt::AlignTop | Qt::AlignLeft); mainLayout->addWidget(pictureLabel, 1, 0, 2, 4); mainLayout->addWidget(mediaInfoLabel, 1, 4, 1, 4, Qt::AlignTop); mainLayout->addWidget(selectMediaButton, 2, 4, 1, 2, Qt::AlignBottom | Qt::AlignLeft); mainLayout->addWidget(uploadProgressBar, 2, 6, 1, 2, Qt::AlignBottom | Qt::AlignRight); mainLayout->addWidget(removeMediaButton, 2, 6, 1, 2, Qt::AlignBottom | Qt::AlignRight); mainLayout->addWidget(composerBox, 3, 0, 3, 8); // 3 rows, 8 columns mainLayout->addWidget(toSelectorButton, 6, 0, 1, 1, Qt::AlignLeft); mainLayout->addWidget(ccSelectorButton, 6, 1, 1, 1, Qt::AlignLeft); mainLayout->addWidget(addMediaButton, 6, 3, 1, 2, Qt::AlignCenter); mainLayout->addWidget(statusInfoLabel, 6, 5, 3, 2, Qt::AlignCenter); mainLayout->addWidget(postButton, 6, 7, 1, 1); // The 2 labels holding people's names, if any mainLayout->addWidget(toAudienceLabel, 7, 0, 1, 1); mainLayout->addWidget(ccAudienceLabel, 7, 1, 1, 1); // The 2 labels holding "public/followers", if selected mainLayout->addWidget(toPublicFollowersLabel, 8, 0, 1, 1); mainLayout->addWidget(ccPublicFollowersLabel, 8, 1, 1, 1); // Character counter mainLayout->addWidget(charCounterLabel, 8, 3, 1, 2, Qt::AlignCenter); // The "Cancel post" button mainLayout->addWidget(cancelButton, 8, 7, 1, 1); #ifdef GROUPSUPPORT groupIdLabel = new QLabel("TO GROUP (ID):"); mainLayout->addWidget(groupIdLabel, 9, 0, 1, 1); groupIdLineEdit = new QLineEdit(); mainLayout->addWidget(groupIdLineEdit, 9, 1, 1, 7); #endif this->setLayout(mainLayout); this->setMinimumMode(); qDebug() << "Publisher created"; } Publisher::~Publisher() { qDebug() << "Publisher destroyed"; } /* * Set if "public" option for audience is checked as default * */ void Publisher::syncFromConfig() { this->defaultPublicPosting = this->globalObj->getPublicPostsByDefault(); this->showCharacterCounter = this->globalObj->getShowCharacterCounter(); // Ensure status is sync'ed this->toSelectorMenu->actions().first()->setChecked(this->defaultPublicPosting); } /* * Set default "no photo" pixmap and "picture/audio/video not set" message * * Clear the filename and content type variables too */ void Publisher::setEmptyMediaData() { pictureLabel->setToolTip(""); mediaInfoLabel->clear(); this->mediaFilename.clear(); this->mediaContentType.clear(); } /* * Set title and content from the outside, for automation (dbus interface) * */ void Publisher::setTitleAndContent(QString title, QString content) { QString statusText; if (this->titleLineEdit->text().isEmpty() && this->composerBox->toPlainText().isEmpty()) { this->titleLineEdit->setText(title.trimmed()); this->composerBox->setText(content.trimmed()); statusText = tr("Note started from another application."); // FIXME? } else { statusText = tr("Ignoring new note request from another application."); // TMP message FIXME } this->statusInfoLabel->setText(statusText); } void Publisher::updatePublicFollowersLabels() { QString toString; foreach (QString listName, toListsNameStringList) { toString.append(QString::fromUtf8("\342\236\224 ") // arrow sign in front + listName + "\n"); } if (toPublicAction->isChecked()) { toString.append("+" + tr("Public") + "\n"); } if (toFollowersAction->isChecked()) { toString.append("+" + tr("Followers") + "\n"); } toPublicFollowersLabel->setText(toString); QString ccString; foreach (QString listName, ccListsNameStringList) { ccString.append(QString::fromUtf8("\342\236\224 ") // arrow sign + listName + "\n"); } if (ccPublicAction->isChecked()) { ccString.append("+" + tr("Public") + "\n"); } if (ccFollowersAction->isChecked()) { ccString.append("+" + tr("Followers") + "\n"); } ccPublicFollowersLabel->setText(ccString); } /* * Create a key:value map, listing who will receive a post, like: * * "collection" : "http://activityschema.org/collection/public" * "collection" : "https://pump.example/api/user/followers" * "group" : "https://pump.example/api/user/group/someGroupId123" * "person" : "acct:somecontact@pumpserver.example" * */ QMap Publisher::getAudienceMap() { QMap audienceMap; onlyToFollowers = true; // Will be set to false if something else is enabled // Used to warn the user when posting only to followers having none // To: Public is checked if (toPublicAction->isChecked()) { onlyToFollowers = false; audienceMap.insertMulti("to|collection", "http://activityschema.org/collection/public"); } // To: List of individual people foreach (QString address, this->toAddressStringList) { onlyToFollowers = false; audienceMap.insertMulti("to|person", "acct:" + address); } // To: Lists foreach (QString address, this->toListsIdStringList) { onlyToFollowers = false; audienceMap.insertMulti("to|collection", address); } // To: Followers is checked if (toFollowersAction->isChecked()) { audienceMap.insertMulti("to|collection", this->pController->currentFollowersUrl()); } // Cc: Public is checked if (ccPublicAction->isChecked()) { onlyToFollowers = false; audienceMap.insertMulti("cc|collection", "http://activityschema.org/collection/public"); } // Cc: List of individual people foreach (QString address, this->ccAddressStringList) { onlyToFollowers = false; audienceMap.insertMulti("cc|person", "acct:" + address); } // Cc: Lists foreach (QString address, this->ccListsIdStringList) { onlyToFollowers = false; audienceMap.insertMulti("cc|collection", address); } #ifdef GROUPSUPPORT // Cc: Groups if (!groupIdLineEdit->text().trimmed().isEmpty()) { onlyToFollowers = false; audienceMap.insertMulti("to|group", groupIdLineEdit->text().trimmed()); } #endif // Last check: if Cc: Followers is checked, or nothing is, add Followers if (ccFollowersAction->isChecked() || audienceMap.isEmpty()) { // FIXME: maybe fail and warn the user instead audienceMap.insertMulti("cc|collection", this->pController->currentFollowersUrl()); } return audienceMap; } void Publisher::setMediaModeWidgets() { this->addMediaButton->setDisabled(true); this->pictureLabel->show(); this->mediaInfoLabel->show(); this->selectMediaButton->show(); this->removeMediaButton->show(); } /* * Disable some widgets while sending a post, * including during media upload * */ void Publisher::toggleWidgetsWhileSending(bool widgetsEnabled) { this->titleLineEdit->setEnabled(widgetsEnabled); this->composerBox->setEnabled(widgetsEnabled); this->toolsButton->setEnabled(widgetsEnabled); this->toSelectorButton->setEnabled(widgetsEnabled); this->ccSelectorButton->setEnabled(widgetsEnabled); // Only re-enable "add" button if this was still a simple note if (postType == "note") { this->addMediaButton->setEnabled(widgetsEnabled); } this->selectMediaButton->setEnabled(widgetsEnabled); this->removeMediaButton->setEnabled(widgetsEnabled); this->postButton->setEnabled(widgetsEnabled); } bool Publisher::isFullMode() { return this->fullMode; } /********************************************************************/ /***************************** SLOTS ********************************/ /********************************************************************/ void Publisher::setMinimumMode() { qDebug() << "setting Publisher to minimum mode"; this->postButton->setFocus(); // Give focus to button, // in case user shared with Ctrl+Enter // Disable possible scrollbars this->composerBox->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); this->composerBox->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Disable Ctrl+Shift+V for plaintext paste, to avoid conflict with Commenters this->composerBox->setPlainPasteEnabled(false); // ~1 row int composerHeight = composerBox->getMessageLabelHeight(); this->composerBox->setMinimumHeight(composerHeight); this->composerBox->setMaximumHeight(composerHeight); this->setMinimumHeight(composerHeight + 2); this->setMaximumHeight(composerHeight + 2); this->audienceSelectorTo->deletePrevious(); this->audienceSelectorTo->resetLists(); this->audienceSelectorCC->deletePrevious(); this->audienceSelectorCC->resetLists(); this->toAudienceLabel->setText("..."); toAudienceLabel->repaint(); // Avoid a flicker-like effect later toAudienceLabel->clear(); toAudienceLabel->hide(); this->toAddressStringList.clear(); this->toListsNameStringList.clear(); this->toListsIdStringList.clear(); toPublicFollowersLabel->clear(); toPublicFollowersLabel->hide(); this->ccAudienceLabel->setText("..."); ccAudienceLabel->repaint(); ccAudienceLabel->clear(); ccAudienceLabel->hide(); this->ccAddressStringList.clear(); this->ccListsNameStringList.clear(); this->ccListsIdStringList.clear(); ccPublicFollowersLabel->clear(); ccPublicFollowersLabel->hide(); this->toSelectorButton->hide(); this->ccSelectorButton->hide(); // Check "public" if "public posts" is set in the preferences toPublicAction->setChecked(this->defaultPublicPosting); toFollowersAction->setChecked(false); ccPublicAction->setChecked(false); ccFollowersAction->setChecked(true); // Cc: Followers by default // Uncheck the lists, very TMP / FIXME // This doesn't clear the To/Cc labels sometimes foreach (QAction *action, toSelectorListsMenu->actions()) { action->setChecked(false); } foreach (QAction *action, ccSelectorListsMenu->actions()) { action->setChecked(false); } titleLabel->hide(); this->titleLineEdit->clear(); titleLineEdit->hide(); toolsButton->hide(); this->statusInfoLabel->clear(); statusInfoLabel->hide(); this->addMediaButton->hide(); this->charCounterLabel->hide(); this->postButton->hide(); this->cancelButton->hide(); // Clear formatting options like bold, italic and underline this->composerBox->setCurrentCharFormat(QTextCharFormat()); // Hide "media mode" controls this->cancelMediaMode(); // Clear "editing mode", restore stuff if (editingMode) { this->editingMode = false; this->editingPostId.clear(); this->postButton->setText(tr("Post", "verb")); // Button text back to "Post" as usual this->toSelectorButton->setEnabled(true); this->ccSelectorButton->setEnabled(true); } this->fullMode = false; #ifdef GROUPSUPPORT this->groupIdLabel->hide(); this->groupIdLineEdit->hide(); this->groupIdLineEdit->clear(); #endif } void Publisher::setFullMode() { qDebug() << "setting Publisher to full mode"; this->titleLabel->show(); this->titleLineEdit->show(); this->toolsButton->show(); this->composerBox->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); this->composerBox->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); this->composerBox->setMaximumHeight(2048); this->composerBox->hideInfoMessage(); this->setMaximumHeight(2048); // i.e. "unlimited" // Re-enable ctrl+shift+v for plaintext paste (disabled to avoid conflicts) this->composerBox->setPlainPasteEnabled(true); this->toSelectorButton->show(); this->ccSelectorButton->show(); this->toAudienceLabel->show(); this->toPublicFollowersLabel->show(); this->ccAudienceLabel->show(); this->ccPublicFollowersLabel->show(); updatePublicFollowersLabels(); // Avoid re-enabling the media button when re-focusing publisher, but still in media mode... if (addMediaButton->isHidden()) { this->addMediaButton->setEnabled(true); // If it wasn't hidden, don't re-enable } this->addMediaButton->show(); this->statusInfoLabel->show(); this->charCounterLabel->setVisible(globalObj->getShowCharacterCounter()); this->postButton->show(); this->cancelButton->show(); this->composerBox->setFocus(); // In case user used menu or shortcut // instead of clicking on it this->fullMode = true; #ifdef GROUPSUPPORT this->groupIdLabel->show(); this->groupIdLineEdit->show(); #endif } void Publisher::setPictureMode() { postType = "image"; setMediaModeWidgets(); pictureLabel->setPixmap(QIcon::fromTheme("image-x-generic", QIcon(":/images/attached-image.png")) .pixmap(200, 150) .scaled(200, 150, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); selectMediaButton->setIcon(QIcon::fromTheme("folder-image")); selectMediaButton->setText(tr("Select Picture...")); selectMediaButton->setToolTip("" + tr("Find the picture in your folders")); pictureLabel->setToolTip(tr("Picture not set")); this->findMediaFile(); // Show the open file dialog directly } void Publisher::setAudioMode() { postType = "audio"; setMediaModeWidgets(); pictureLabel->setPixmap(QIcon::fromTheme("audio-x-generic", QIcon(":/images/attached-audio.png")) .pixmap(200, 150) .scaled(200, 150, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); selectMediaButton->setIcon(QIcon::fromTheme("folder-sound")); selectMediaButton->setText(tr("Select Audio File...")); selectMediaButton->setToolTip("" + tr("Find the audio file in your folders")); pictureLabel->setToolTip(tr("Audio file not set")); this->findMediaFile(); // Show the open file dialog directly } void Publisher::setVideoMode() { postType = "video"; setMediaModeWidgets(); pictureLabel->setPixmap(QIcon::fromTheme("video-x-generic", QIcon(":/images/attached-video.png")) .pixmap(200, 150) .scaled(200, 150, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); selectMediaButton->setIcon(QIcon::fromTheme("folder-video")); selectMediaButton->setText(tr("Select Video...")); selectMediaButton->setToolTip("" + tr("Find the video in your folders")); pictureLabel->setToolTip(tr("Video not set")); this->findMediaFile(); // Show the open file dialog directly } void Publisher::setFileMode() { postType = "file"; setMediaModeWidgets(); pictureLabel->setPixmap(QIcon::fromTheme("application-octet-stream", QIcon(":/images/attached-file.png")) .pixmap(200, 150) .scaled(200, 150, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); selectMediaButton->setIcon(QIcon::fromTheme("folder")); selectMediaButton->setText(tr("Select File...")); selectMediaButton->setToolTip("" + tr("Find the file in your folders")); pictureLabel->setToolTip(tr("File not set")); this->findMediaFile(); // Show the open file dialog directly } /* * Remove the attachment from the publisher * */ void Publisher::cancelMediaMode() { this->addMediaButton->setEnabled(true); this->pictureLabel->hide(); this->mediaInfoLabel->hide(); this->selectMediaButton->hide(); this->removeMediaButton->hide(); this->uploadProgressBar->hide(); this->setEmptyMediaData(); this->postType = "note"; } /* * Set Publiser to edit mode, after user clicks on "Edit" in a post * */ void Publisher::setEditingMode(QString postId, QString postType, QString postTitle, QString postText) { // Prevent the "Edit" option from destroying a post currently being composed! if (editingMode || fullMode) { QMessageBox::warning(this, tr("Error: Already composing"), tr("You can't edit a post at this time, " "because a post is already being composed.")); return; } this->editingMode = true; this->editingPostId = postId; this->postType = postType; setFullMode(); // Fill in the contents of the post this->titleLineEdit->setText(postTitle); this->composerBox->setText(postText); // Change/disable some controls this->postButton->setText(tr("Update")); this->toSelectorButton->setDisabled(true); this->ccSelectorButton->setDisabled(true); this->addMediaButton->setDisabled(true); // Clear these, so it doesn't look like the audience is different than when originally posted toPublicAction->setChecked(false); toFollowersAction->setChecked(false); ccPublicAction->setChecked(false); ccFollowersAction->setChecked(false); this->toAudienceLabel->clear(); this->ccAudienceLabel->clear(); this->statusInfoLabel->setText(tr("Editing post")); } void Publisher::startMessageForContact(QString id, QString name, QString url) { if (name.trimmed().isEmpty()) // Ensure "name" has some text in it { name = id; } if (fullMode) { QMessageBox::warning(this, tr("Error: Already composing"), tr("You can't create a message for %1 at " "this time, because a post is already " "being composed.").arg(name)); return; } setFullMode(); // Unselect Public and Followers from To/Cc toPublicAction->setChecked(false); toFollowersAction->setChecked(false); ccPublicAction->setChecked(false); ccFollowersAction->setChecked(false); // Add user name/ID to the "To" field... this->audienceSelectorTo->copyToSelected(QIcon::fromTheme("user-identity", QIcon(":/images/no-avatar.png")), QString("%1 <%2>").arg(name).arg(id), url); this->audienceSelectorTo->setAudience(); // Set a default title... FIXME? this->titleLineEdit->setText(name + ":"); } /* * Add name + id to selected recipients when auto-completing a nick * * 'name' can always be expected to have a value * */ void Publisher::addNickToRecipients(QString id, QString name, QString url) { // If for some reason a name is received without an ID, do nothing if (id.trimmed().isEmpty()) { qDebug() << "Trying to add a nick to recipients without an ID!" << name << " - " << url; return; } /* TMP/FIXME: Until audience can be modified when editing, To/Cc buttons * are disabled, and so must be the nick autocompletion. Not actually * disabled, but nick won't be added to the audience lists, since it * wouldn't really do anything. * */ if (editingMode) { qDebug() << "Publisher: Can't modify audience in EDITING mode"; return; } this->audienceSelectorTo->copyToSelected(QIcon::fromTheme("user-identity", // FIXME QIcon(":/images/no-avatar.png")), QString("%1 <%2>").arg(name).arg(id), url); this->audienceSelectorTo->setAudience(); } /* * After the post is confirmed to have been received by the server * re-enable publisher, clear text, etc. * */ void Publisher::onPublishingOk() { this->statusInfoLabel->clear(); this->toggleWidgetsWhileSending(true); this->composerBox->erase(); // Done composing message, hide buttons until we get focus again setMinimumMode(); } /* * If there was an HTTP error while posting... * */ void Publisher::onPublishingFailed() { qDebug() << "Posting failed, re-enabling Publisher"; this->statusInfoLabel->setText(tr("Posting failed.\n\nTry again.")); this->toggleWidgetsWhileSending(true); this->uploadProgressBar->hide(); // In media mode, show the "remove" button again (hidden during upload) if (this->postType != "note") { this->removeMediaButton->show(); } this->composerBox->setFocus(); } /* * These are called when selecting Public or Followers in the menus * * When selecting "Cc: Followers", "To: Followers" gets unselected, etc. * */ void Publisher::setToPublic(bool activated) { if (activated) { ccPublicAction->setChecked(false); // Unselect "Cc: Public" } updatePublicFollowersLabels(); } void Publisher::setToFollowers(bool activated) { if (activated) { ccFollowersAction->setChecked(false); // Unselect "Cc: Followers" } updatePublicFollowersLabels(); } void Publisher::setCCPublic(bool activated) { if (activated) { toPublicAction->setChecked(false); // Unselect "To: Public" } updatePublicFollowersLabels(); } void Publisher::setCCFollowers(bool activated) { if (activated) { toFollowersAction->setChecked(false); // Unselect "To: Followers" } updatePublicFollowersLabels(); } /* * Receive a list of addresses for the To or Cc fields * */ void Publisher::updateToCcFields(QString selectorType, QStringList contactsList, QStringList urlsList) { qDebug() << "Publisher::updateToCcFields()" << selectorType << contactsList; QRegExp contactRE("(.+)\\s+\\<(.+@.+)\\>", Qt::CaseInsensitive); contactRE.setMinimal(true); QString addressesString; QStringList addressesStringList; QString contactName; QString contactId; QString contactUrl; int urlCounter = 0; int urlsListLast = urlsList.size() - 1; foreach (QString contactString, contactsList) { contactRE.indexIn(contactString); contactName = contactRE.cap(1).trimmed(); contactId = contactRE.cap(2).trimmed(); if (contactName.isEmpty()) { contactName = contactId; } contactUrl.clear(); if (urlCounter <= urlsListLast) // Ensure stringlist position is valid { // Works even if the list is empty: 0 <= -1 contactUrl = urlsList.at(urlCounter); } // Append to the visible names string to be displayed under the composer addressesString.append("" + contactName + ""); if (urlCounter < urlsListLast) // Not in the last item yet { addressesString.append(", "); ++urlCounter; } // Add also to the simple ID list for getAudienceMap() addressesStringList.append(contactId); } if (selectorType == "to") { this->toAudienceLabel->setText(addressesString); this->toAddressStringList = addressesStringList; } else // "cc" { this->ccAudienceLabel->setText(addressesString); this->ccAddressStringList = addressesStringList; } qDebug() << "Names:" << addressesString; qDebug() << "Addresses:" << addressesStringList; } /* * Fill the "Lists" submenus with PumpController's lists info * * */ void Publisher::updateListsMenus(QVariantList listsList) { // First, clear the menus toSelectorListsMenu->clear(); // clear() should delete the actions ccSelectorListsMenu->clear(); if (listsList.length() > 0) // if there are some lists, enable the menu { toSelectorListsMenu->setEnabled(true); ccSelectorListsMenu->setEnabled(true); } QString listName; QVariant listId; foreach (QVariant list, listsList) { listName = list.toMap().value("displayName").toString(); listId = list.toMap().value("id"); QAction *toListAction = new QAction(listName, this); toListAction->setCheckable(true); toListAction->setData(listId); toSelectorListsMenu->addAction(toListAction); QAction *ccListAction = new QAction(listName, this); ccListAction->setCheckable(true); ccListAction->setData(listId); ccSelectorListsMenu->addAction(ccListAction); } } void Publisher::updateToListsFields(QAction *listAction) { QString listName = listAction->text(); QString listId = listAction->data().toString(); if (listAction->isChecked()) { qDebug() << "To list selected" << listName << listId; toListsNameStringList.append(listName); toListsIdStringList.append(listId); } else { qDebug() << "To list unselected" << listName; toListsNameStringList.removeAll(listName); toListsIdStringList.removeAll(listId); } this->updatePublicFollowersLabels(); qDebug() << "Current To Lists:" << toListsNameStringList << toListsIdStringList; } void Publisher::updateCcListsFields(QAction *listAction) { QString listName = listAction->text(); QString listId = listAction->data().toString(); if (listAction->isChecked()) { qDebug() << "Cc list selected" << listName << listId; ccListsNameStringList.append(listName); ccListsIdStringList.append(listId); } else { qDebug() << "Cc list unselected" << listName; ccListsNameStringList.removeAll(listName); ccListsIdStringList.removeAll(listId); } this->updatePublicFollowersLabels(); qDebug() << "Current Cc Lists:" << ccListsNameStringList << ccListsIdStringList; } /* * Show the URL of a contact in the recipients list, in the status bar * * FIXME: merge with Post::showHighlightedUrl() * */ void Publisher::showHighlightedUrl(QString url) { if (!url.isEmpty()) { this->pController->showTransientMessage(url); qDebug() << "Highlighted recipient url in publisher:" << url; } else { this->pController->showTransientMessage(""); } } /* * Send the post (note, image, audio...) to the server * */ void Publisher::sendPost() { qDebug() << "Publisher character count:" << composerBox->textCursor() .document()->characterCount(); bool textIsEmpty; if (composerBox->textCursor().document()->characterCount() > 1) // kinda tmp { textIsEmpty = false; } else { textIsEmpty = true; } // If there's some text in the post, or attached media, send it if ( (postType == "note" && !textIsEmpty) || (postType != "note" && !mediaFilename.isEmpty()) || editingMode ) // Or editing, then the other stuff doesn't matter { QString postTitle = this->titleLineEdit->text().trimmed(); postTitle.replace("\n", " "); // Post title could have newlines if copy-pasted if (postTitle.length() > 120) // Limit title length to something sane { postTitle = postTitle.left(115) + " [...]"; } QString cleanHtmlString = MiscHelpers::cleanupHtml(composerBox->toHtml()); QMap audienceMap = this->getAudienceMap(); // Warn user if posting only to Followers, but having none if (this->onlyToFollowers // Set in getAudienceMap() && this->pController->currentFollowersCount() == 0 && !editingMode) { qDebug() << "WARNING: You have no followers yet; Post to public?"; int action = QMessageBox::warning(this, tr("Warning: You have no followers yet"), tr("You're trying to post to your followers " "only, but you don't have any followers " "yet.") + " " + tr("If you post like this, no one will be " "able to see your message.") + "\n\n" + tr("Do you want to make the post public " "instead of followers-only?") + "\n", tr("&Yes, make it public"), tr("&No, post to my followers only"), tr("&Cancel, go back to the post"), 2, 2); // Cancel is default, and also activated with ESC if (action == 0) { this->toPublicAction->setChecked(true); // Check To:Public audienceMap = this->getAudienceMap(); // Process again qDebug() << "Checked To:Public"; } else if (action == 2) { qDebug() << "Posting aborted; back to post composer"; return; } } // Don't erase just yet!! Just disable until we get "200 OK" from the server. this->toggleWidgetsWhileSending(false); if (!editingMode) { this->statusInfoLabel->setText(tr("Posting...")); if (postType == "note") { this->pController->postNote(audienceMap, cleanHtmlString, postTitle); } else { uploadNetworkReply = this->pController->postMedia(audienceMap, cleanHtmlString, postTitle, mediaFilename, postType, mediaContentType); connect(uploadNetworkReply, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(updateProgressBar(qint64,qint64))); // This will be automatically disconnected when // the networkReply is automatically deleted! // The "remove" button uses the same space as the progress bar removeMediaButton->hide(); uploadProgressBar->setValue(0); uploadProgressBar->show(); } } else { this->statusInfoLabel->setText(tr("Updating...")); this->pController->updatePost(this->editingPostId, this->postType, cleanHtmlString, postTitle); } } else { if (this->postType == "note") { this->statusInfoLabel->setText(tr("Post is empty.")); } else // image, audio... { this->statusInfoLabel->setText(tr("File not selected.")); } qDebug() << "Can't send post: text is empty, or no media"; } } /* * Let the user find a file in his/her folders, for media upload * */ void Publisher::findMediaFile() { QString findDialogTitle; QString mediaTypes; QString errorTitle; QString errorMessage; if (postType == "image") { findDialogTitle = tr("Select one image"); mediaTypes = tr("Image files") + " (*.png *.jpg *.jpeg *.jpe" " *.gif *.mng *.webp *.svg);;"; errorTitle = tr("Invalid image"); errorMessage = tr("The image format cannot be detected.\n" "The extension might be wrong, like a GIF " "image renamed to image.jpg or similar."); } else if (postType == "audio") { findDialogTitle = tr("Select one audio file"); mediaTypes = tr("Audio files") + " (*.ogg *.oga *.flac *.mka" " *.mp3 *.mpga *.wav *.wma);;"; errorTitle = tr("Invalid audio file"); errorMessage = tr("The audio format cannot be detected."); } else if (postType == "video") { findDialogTitle = tr("Select one video file"); mediaTypes = tr("Video files") + " (*.avi *.mpg *.mpeg *.mpe *.mpv" " *.ogm *.ogg *.mkv *.mp4 *.webm" " *.mov *.flv *.3gp *.wmv);;"; errorTitle = tr("Invalid video file"); errorMessage = tr("The video format cannot be detected."); } else // File { findDialogTitle = tr("Select one file"); mediaTypes = ""; errorTitle = tr("Invalid file"); errorMessage = tr("The file type cannot be detected."); } QString filename; filename = QFileDialog::getOpenFileName(this, findDialogTitle, this->lastUsedDirectory, mediaTypes + tr("All files") + " (*)"); if (!filename.isEmpty()) { qDebug() << "Selected" << filename << "for upload"; QFileInfo fileInfo(filename); this->mediaContentType = MiscHelpers::getFileMimeType(filename); // Temporary protection; https://github.com/e14n/pump.io/issues/1015 if (fileInfo.size() > 10485760) // 10 MiB; this protects the servers from abuse { QString bigImageNote; if (postType == "image") { bigImageNote = "\n\n" + tr("Since you're uploading an image, you could " "scale it down a little or save it in a " "more compressed format, like JPG."); } QMessageBox::warning(this, tr("File is too big"), tr("Dianara currently limits file uploads " "to 10 MiB per post, to prevent possible " "storage or network problems in the " "servers.") + "\n\n" + tr("This is a temporary measure, since " "the servers cannot set their " "own limits yet.") + bigImageNote + "\n\n" + tr("Sorry for the inconvenience.")); } else if (!mediaContentType.isEmpty()) { this->pictureLabel->setToolTip("" // wordwrapped + filename); this->mediaFilename = filename; QString metaDataString; if (postType == "image") { QPixmap imagePixmap = QPixmap(filename); if (imagePixmap.isNull()) { QMessageBox::warning(this, errorTitle, errorMessage); } this->pictureLabel->setPixmap(imagePixmap.scaled(320, 180, // 16:9 Qt::KeepAspectRatio, Qt::SmoothTransformation)); metaDataString = QString("

" "" +tr("Resolution") + ": %1x%2") .arg(imagePixmap.width()) .arg(imagePixmap.height()); } this->lastUsedDirectory = fileInfo.path(); qDebug() << "last used directory:" << lastUsedDirectory; mediaInfoLabel->setText(QString("%1" "
" "" + tr("Type") + ": %2" "
" "" + tr("Size") + ": %3") .arg(fileInfo.fileName(), mediaContentType, MiscHelpers::fileSizeString(filename)) + metaDataString); // If there's no title, set one from the filename if (this->titleLineEdit->text().trimmed().isEmpty()) { // But only if configured to do so if (this->globalObj->getUseFilenameAsTitle()) { QString titleFromFilename = fileInfo.fileName(); // .baseName() ? titleFromFilename.replace("_", " "); titleFromFilename.replace(".", " "); this->titleLineEdit->setText(titleFromFilename); } } } else { qDebug() << "Unknown " << postType << " format; Extension is probably wrong"; QMessageBox::warning(this, errorTitle, errorMessage); this->mediaContentType.clear(); this->mediaFilename.clear(); this->pictureLabel->setToolTip(""); } this->uploadProgressBar->hide(); } } void Publisher::updateProgressBar(qint64 sent, qint64 total) { this->uploadProgressBar->setRange(0, total); this->uploadProgressBar->setValue(sent); this->uploadProgressBar->setToolTip(tr("%1 KiB of %2 KiB uploaded") .arg(QLocale::system() .toString(sent / 1024)) .arg(QLocale::system() .toString(total / 1024))); } void Publisher::updateCharacterCounter() { int charCount = composerBox->textCursor().document()->characterCount() - 1; this->charCounterLabel->setText(QString("%1") .arg(QLocale::system().toString(charCount))); } dianara-v1.3.2/src/composer.cpp000644 000764 000764 00000065401 12614520205 016021 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "composer.h" Composer::Composer(GlobalObject *globalObject, bool forPublisher, QWidget *parent) : QTextEdit(parent) { this->globalObj = globalObject; this->forPublisher = forPublisher; this->setAcceptRichText(true); this->setTabChangesFocus(true); QFont startConversationFont; startConversationFont.setPointSize(startConversationFont.pointSize() - 2); startConversationLabel = new QLabel(tr("Click here or press Control+N " "to post a note..."), this); startConversationLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); startConversationLabel->setFont(startConversationFont); // A menu to insert some Unicode symbols symbolsMenu = new QMenu(tr("Symbols"), this); symbolsMenu->setIcon(QIcon::fromTheme("character-set")); symbolsMenu->addAction(QString::fromUtf8("\342\230\272")); // Smiling face symbolsMenu->addAction(QString::fromUtf8("\342\230\271")); // Sad face symbolsMenu->addAction(QString::fromUtf8("\342\231\245")); // Heart symbolsMenu->addAction(QString::fromUtf8("\342\231\253")); // Musical note symbolsMenu->addAction(QString::fromUtf8("\342\230\225")); // Coffee symbolsMenu->addAction(QString::fromUtf8("\342\234\224")); // Check mark symbolsMenu->addAction(QString::fromUtf8("\342\234\230")); // Ballot X symbolsMenu->addAction(QString::fromUtf8("\342\230\205")); // Black star symbolsMenu->addAction(QString::fromUtf8("\342\254\205")); // Arrow to the left symbolsMenu->addAction(QString::fromUtf8("\342\236\241")); // Arrow to the right symbolsMenu->addAction(QString::fromUtf8("\342\231\273")); // Recycling symbol symbolsMenu->addAction(QString::fromUtf8("\342\210\236")); // Infinity connect(symbolsMenu, SIGNAL(triggered(QAction*)), this, SLOT(insertSymbol(QAction*))); toolsMenu = new QMenu(tr("Formatting"), this); toolsMenu->addAction(QIcon::fromTheme(""), tr("Normal"), this, SLOT(makeNormal())); toolsMenu->addAction(QIcon::fromTheme("format-text-bold"), tr("Bold"), this, SLOT(makeBold()), QKeySequence("Ctrl+B")); toolsMenu->addAction(QIcon::fromTheme("format-text-italic"), tr("Italic"), this, SLOT(makeItalic()), QKeySequence("Ctrl+I")); toolsMenu->addAction(QIcon::fromTheme("format-text-underline"), tr("Underline"), this, SLOT(makeUnderline()), QKeySequence("Ctrl+U")); toolsMenu->addAction(QIcon::fromTheme("format-text-strikethrough"), tr("Strikethrough"), this, SLOT(makeStrikethrough())); toolsMenu->addSeparator(); toolsMenu->addAction(QIcon::fromTheme("format-font-size-more"), tr("Header"), this, SLOT(makeHeader()), QKeySequence("Ctrl+H")); toolsMenu->addAction(QIcon::fromTheme("format-list-unordered"), tr("List"), this, SLOT(makeList())); toolsMenu->addAction(QIcon::fromTheme("insert-table"), tr("Table"), this, SLOT(makeTable())); toolsMenu->addAction(QIcon::fromTheme("format-justify-fill"), tr("Preformatted block"), this, SLOT(makePreformatted())); toolsMenu->addAction(QIcon::fromTheme("format-text-italic"), tr("Quote block"), this, SLOT(makeQuote()), QKeySequence("Ctrl+O")); toolsMenu->addSeparator(); toolsMenu->addAction(QIcon::fromTheme("insert-link"), tr("Make a link"), this, SLOT(makeLink()), QKeySequence("Ctrl+L")); toolsMenu->addAction(QIcon::fromTheme("insert-image"), tr("Insert an image from a web site"), this, SLOT(insertImage()), QKeySequence("Ctrl+P")); toolsMenu->addAction(QIcon::fromTheme("insert-horizontal-rule"), tr("Insert line"), this, SLOT(insertLine())); toolsMenu->addSeparator(); toolsMenu->addMenu(symbolsMenu); toolsButton = new QPushButton(QIcon::fromTheme("format-list-ordered", QIcon(":/images/button-configure.png")), tr("&Format", "Button for text formatting and related options")); toolsButton->setMenu(toolsMenu); toolsButton->setToolTip("" + tr("Text Formatting Options")); // Extra action for the context menu, paste as plaintext this->pastePlaintextAction = new QAction(QIcon::fromTheme("paste"), tr("Paste Text Without Formatting"), this); pastePlaintextAction->setShortcut(QKeySequence("Ctrl+Shift+V")); connect(pastePlaintextAction, SIGNAL(triggered()), this, SLOT(pasteAsPlaintext())); // Add action to this object, in order for the shortcut to work // Otherwise, the action isn't available until the context menu is present this->addAction(pastePlaintextAction); // Nick completion stuff nickCompleter = new QCompleter(globalObj->getNickCompletionModel(), this); nickCompleter->setWidget(this); nickCompleter->setMaxVisibleItems(15); nickCompleter->setCompletionColumn(0); nickCompleter->setCaseSensitivity(Qt::CaseInsensitive); nickCompleter->setModelSorting(QCompleter::CaseSensitivelySortedModel); // TMP; FIXME connect(nickCompleter, SIGNAL(activated(QModelIndex)), this, SLOT(insertCompletedNick(QModelIndex))); popupTableView = new QTableView(this); popupTableView->horizontalHeader()->hide(); popupTableView->verticalHeader()->hide(); popupTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); popupTableView->setAlternatingRowColors(true); popupTableView->setSelectionBehavior(QAbstractItemView::SelectRows); popupTableView->setSelectionMode(QAbstractItemView::SingleSelection); popupTableView->setEditTriggers(QAbstractItemView::NoEditTriggers); nickCompleter->setPopup(popupTableView); mainLayout = new QHBoxLayout(); if (this->forPublisher) // Publisher mode { this->setToolTip("" + tr("Type a message here to post it")); mainLayout->addWidget(startConversationLabel, 1, Qt::AlignLeft | Qt::AlignVCenter); } else // or Commenter mode { this->setToolTip("" + tr("Type a comment here")); startConversationLabel->hide(); // Since it has a parent but it's not used, hide it } this->setLayout(mainLayout); qDebug() << "Composer box created"; } Composer::~Composer() { qDebug() << "Composer box destroyed"; } void Composer::erase() { this->clear(); if (this->forPublisher) { startConversationLabel->show(); } } void Composer::insertLink(QString url, QString title) { bool prettyLink = true; if (title.isEmpty()) { prettyLink = false; title = url; } this->insertHtml("" + title + ""); // Space after, when there's no text after the link, // or the link is inserted without selecting text before, // so what the user types next is regular text if (this->textCursor().atEnd() || !prettyLink) { this->insertHtml(" "); // Could use this->makeNormal(), but has some drawbacks } } void Composer::requestCompletion(QString partialNick) { nickCompleter->setCompletionPrefix(partialNick); popupTableView->ensurePolished(); popupTableView->resizeColumnsToContents(); popupTableView->resizeRowsToContents(); popupTableView->ensurePolished(); int tableWidth = popupTableView->columnWidth(0) + popupTableView->columnWidth(1) + 2; if (tableWidth > this->width() + 2) { tableWidth = this->width() + 2; popupTableView->setColumnWidth(0, (tableWidth / 4) * 3); popupTableView->setColumnWidth(1, tableWidth / 4); } popupTableView->setMinimumWidth(tableWidth); int rows = qMin(popupTableView->model()->rowCount(), 15); int rowHeight = popupTableView->rowHeight(0) + 4; popupTableView->setMinimumHeight(rows * rowHeight); // Popup might get disabled at some point when Composer is used in a Post popupTableView->setEnabled(true); nickCompleter->complete(QRect(this->cursorRect().x(), this->cursorRect().y() + 24, tableWidth, 1)); } int Composer::getMessageLabelHeight() { // kinda TMP / FIXME return (this->startConversationLabel->font().pointSize() * 4) + 4; } /* * Hide placeholder message * */ void Composer::hideInfoMessage() { this->startConversationLabel->hide(); } QPushButton *Composer::getToolsButton() { return this->toolsButton; } /* * Enable or disable the Ctrl+Shift+V action to paste without format * * Needed to avoid this conflict between Publisher and Commenters: * * QAction::eventFilter: Ambiguous shortcut overload: Ctrl+Shift+V * */ void Composer::setPlainPasteEnabled(bool state) { this->pastePlaintextAction->setEnabled(state); } /*****************************************************************************/ /****************************** PROTECTED ************************************/ /*****************************************************************************/ /* * Send a signal when getting focus * */ void Composer::focusInEvent(QFocusEvent *event) { emit focusReceived(); // inform Publisher() or Commenter() that we have focus QTextEdit::focusInEvent(event); // process standard event: allows context menu qDebug() << "Composer box got focus"; } void Composer::keyPressEvent(QKeyEvent *event) { //qDebug() << "Composer::keyPressEvent()"; // Allow cancelling/accepting the autocompletion popup if (this->nickCompleter->popup()->isVisible()) { if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return || event->key() == Qt::Key_Escape) { event->ignore(); return; } } // Control+Enter = Send message (post) if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && event->modifiers() == Qt::ControlModifier) { qDebug() << "Control+Enter was pressed"; emit editingFinished(); } else if (event->key() == Qt::Key_Escape) { qDebug() << "Escape was pressed"; if (this->toPlainText().isEmpty()) { qDebug() << "There was no text, cancelling post"; this->cancelPost(); } } else if (event->key() == Qt::Key_Up && this->textCursor().atStart()) { qDebug() << "KEYPRESS: atStart(); will focus on TITLE field"; emit focusTitleRequested(); } else { QTextEdit::keyPressEvent(event); QTextCursor textCursor = this->textCursor(); textCursor.select(QTextCursor::WordUnderCursor); //if (event->key() == Qt::Key_At) if (textCursor.document()->characterAt(textCursor.selectionStart() - 1) == QChar('@')) { // Show completer this->requestCompletion(textCursor.selectedText()); } else { // Hide it, if it was visible this->nickCompleter->popup()->hide(); } } event->accept(); } /* * For custom context menu * */ void Composer::contextMenuEvent(QContextMenuEvent *event) { this->customContextMenu = this->createStandardContextMenu(); // tools menu before default context menu customContextMenu->insertMenu(customContextMenu->actions().at(0), toolsMenu); customContextMenu->insertSeparator(customContextMenu->actions().at(1)); // And options added after default context menu customContextMenu->addSeparator(); customContextMenu->addAction(pastePlaintextAction); if (this->canPaste()) { pastePlaintextAction->setEnabled(true); } else { pastePlaintextAction->setDisabled(true); } customContextMenu->exec(event->globalPos()); // FIXME: Possible leak... should delete customContextMenu? customContextMenu->deleteLater(); event->accept(); } /* * Intervene when pasting, so we can turn links into real HTML links * */ void Composer::insertFromMimeData(const QMimeData *source) { if (source->hasHtml()) { // It's rich text, so just paste it as is QTextEdit::insertFromMimeData(source); } else { QString pastedText = source->text().trimmed(); if (pastedText.startsWith("http://") || pastedText.startsWith("https://")) { int insertionType = 1; // If link looks like an image, ask the user how to insert it if (pastedText.endsWith(".png", Qt::CaseInsensitive) || pastedText.endsWith(".jpg", Qt::CaseInsensitive) || pastedText.endsWith(".jpeg", Qt::CaseInsensitive) || pastedText.endsWith(".gif", Qt::CaseInsensitive)) { insertionType = QMessageBox::question(this, tr("Insert as image?"), tr("The link you are pasting seems to " "point to an image.") + "\n", tr("Insert as visible image"), // Default option (enter) tr("Insert as link"), // ESC option "", 0, 1); } if (insertionType == 0) // Default button, insert as image { this->insertHtml(""); } else { this->insertLink(pastedText); } } else { if (pastedText.contains("://")) // Probably contains links { qDebug() << "Looking for URL's to htmlize"; QRegExp regExp("(http|https|ftp)://(.*)(\\s|$)"); regExp.setMinimal(true); QString link; int matchedLength = 0; int stringPos = 0; while (matchedLength != -1) { stringPos = regExp.indexIn(pastedText, stringPos); matchedLength = regExp.matchedLength(); qDebug() << regExp.capturedTexts(); link = regExp.cap(0); QString htmlLink; if (!link.isEmpty()) // if not an empty string, replace HTML { htmlLink = "" + link + ""; pastedText.replace(stringPos, matchedLength, htmlLink); } stringPos += htmlLink.length(); } pastedText.replace("\n", "
"); this->insertHtml(pastedText + " "); } else // Just paste original contents { qDebug() << "direct paste"; QTextEdit::insertFromMimeData(source); } } } } /*****************************************************************************/ /******************************** SLOTS **************************************/ /*****************************************************************************/ /* * Remove text formatting from selection, bold, italic, etc. * */ void Composer::makeNormal() { QTextCharFormat charFormat; charFormat.clearForeground(); charFormat.clearBackground(); this->setCurrentCharFormat(charFormat); this->setFocus(); } /* * Make selected text bold * */ void Composer::makeBold() { //qDebug() << this->textCursor().selectionStart() << " -> " << this->textCursor().selectionEnd(); QTextCharFormat charFormat; if (this->currentCharFormat().fontWeight() == QFont::Bold) { charFormat.setFontWeight(QFont::Normal); } else { charFormat.setFontWeight(QFont::Bold); } this->mergeCurrentCharFormat(charFormat); this->setFocus(); // give focus back to text editor } /* * Make selected text italic * */ void Composer::makeItalic() { QTextCharFormat charFormat; charFormat.setFontItalic(!this->currentCharFormat().fontItalic()); this->mergeCurrentCharFormat(charFormat); this->setFocus(); } /* * Underline selected text * */ void Composer::makeUnderline() { QTextCharFormat charFormat; charFormat.setFontUnderline(!this->currentCharFormat().fontUnderline()); this->mergeCurrentCharFormat(charFormat); this->setFocus(); } /* * Strike out selected text * */ void Composer::makeStrikethrough() { QTextCharFormat charFormat; charFormat.setFontStrikeOut(!this->currentCharFormat().fontStrikeOut()); this->mergeCurrentCharFormat(charFormat); this->setFocus(); } /* * Turn the selected text into an

header * */ void Composer::makeHeader() { QString selectedText = this->textCursor().selectedText(); if (!selectedText.isEmpty()) { this->textCursor().removeSelectedText(); this->insertHtml("

" + selectedText + "

"); } this->setFocus(); } void Composer::makeList() { QString selectedText = this->textCursor().selectedText(); if (!selectedText.isEmpty()) { this->textCursor().removeSelectedText(); this->insertHtml("
  • " + selectedText + "

"); } this->setFocus(); } void Composer::makeTable() { const QString dialogTitle = tr("Table Size"); bool inputOk = false; int rows = QInputDialog::getInt(this, dialogTitle, tr("How many rows (height)?") + " " // Make the dialog a little wider than necessary + QString::fromUtf8("\342\207\225") // up-down arrow + "\n\n", 5, 1, 10, 1, &inputOk); if (inputOk) // Rows dialog wasn't cancelled { int columns = QInputDialog::getInt(this, dialogTitle, tr("How many columns (width)?") + " " + QString::fromUtf8("\342\207\224") // left-right arrow + "\n\n", 4, 1, 10, 1, &inputOk); if (inputOk) // Columns dialog wasn't cancelled either { QTextTableFormat tableFormat; tableFormat.setCellPadding(2); tableFormat.setCellSpacing(4); this->textCursor().insertTable(rows, columns, tableFormat); } } this->setFocus(); } /* * Put selected text into a
 block
 *
 */
void Composer::makePreformatted()
{
    QString selectedText = this->textCursor().selectedText();

    if (!selectedText.isEmpty())
    {
        this->textCursor().removeSelectedText();
        this->insertHtml("
" + selectedText + "
"); } this->setFocus(); } /* * Mark selected as quoted, using
* */ void Composer::makeQuote() { QString selectedText = this->textCursor().selectedText(); if (!selectedText.isEmpty()) { this->textCursor().removeSelectedText(); this->insertHtml("  " "
“" + selectedText + "”
" "
"); this->textCursor().deletePreviousChar(); // Delete undesired extra newline } // FIXME: Qt's HTML changes
into its own formatting // so other clients and the web UI might not display it as other people's blockquote's this->setFocus(); } /* * Convert selected text into a link * */ void Composer::makeLink() { QString selectedText = this->textCursor().selectedText(); QString link; if (selectedText.isEmpty()) { link = QInputDialog::getText(this, tr("Insert a link"), tr("Type or paste a web address here.\n" "You could also select some text first, " "to turn it into a link.") + "\n\n", QLineEdit::Normal, "http://"); } else { QString shortenedText = MiscHelpers::elidedText(selectedText, 40); link = QInputDialog::getText(this, tr("Make a link from selected text"), tr("Type or paste a web address here.\n" "The selected text (%1) will be converted " "to a link.").arg(shortenedText) + "\n\n", QLineEdit::Normal, "http://"); } if (!link.isEmpty()) { link = link.trimmed(); // Remove possible spaces before or after this->textCursor().removeSelectedText(); this->insertLink(link, selectedText); } else { qDebug() << "makeLink(): Invalid URL"; } this->setFocus(); } /* * Insert an image from a URL * */ void Composer::insertImage() { // FIXME: should make sure there is no text selected QString imageUrl; imageUrl = QInputDialog::getText(this, tr("Insert an image from a URL"), tr("Type or paste the image address here.\n" "The link must point to the image file directly.") + "\n\n", QLineEdit::Normal, "http://"); if (!imageUrl.isEmpty()) { if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) { this->insertHtml(""); } else { QMessageBox::warning(this, tr("Error: Invalid URL"), tr("The address you entered (%1) " "is not valid.\n" "Image addresses should begin " "with http:// or https://").arg(imageUrl)); } } else { qDebug() << "insertImage(): Image URL is empty"; } this->setFocus(); } /* * Insert a horizontal line,
* */ void Composer::insertLine() { this->insertHtml("

"); this->setFocus(); } void Composer::insertSymbol(QAction *action) { this->insertPlainText(action->text()); this->insertPlainText(" "); this->setFocus(); } void Composer::pasteAsPlaintext() { QString subtype("plain"); QString clipboardContents = QApplication::clipboard()->text(subtype, QClipboard::Clipboard); this->makeNormal(); // Ensure normal text before inserting this->insertPlainText(clipboardContents); } /* * Insert the selected nick chosen from the autocomplete list. * Notify the parent object about it so the user can be added * to To/Cc. * */ void Composer::insertCompletedNick(QModelIndex nickData) { QString nickId = nickData.data(Qt::UserRole + 1).toString(); QString nickName = nickData.data().toString(); QString nickUrl = nickData.data(Qt::UserRole + 2).toString(); // Abort if there's no ID; this can happen if the ID column is clicked if (nickId.isEmpty()) { qDebug() << "*** AUTOCOMPLETER: ID IS EMPTY! (clicked 2nd column?)"; return; } QTextCursor textCursor = this->textCursor(); textCursor.select(QTextCursor::WordUnderCursor); textCursor.removeSelectedText(); this->insertHtml("" + nickName + ""); this->makeNormal(); // Send signal for Publisher(), to add to the "To" field emit nickInserted(nickId, nickName, nickUrl); } /* * Cancel editing of the post, clear it, return to minimum mode * */ void Composer::cancelPost() { int cancelConfirmed = 1; if (this->document()->isEmpty()) { cancelConfirmed = 0; // Cancelling doesn't need confirmation if it's empty } else { cancelConfirmed = QMessageBox::question(this, tr("Cancel message?"), tr("Are you sure you want to cancel this message?"), tr("&Yes, cancel it"), tr("&No"), "", 1, 1); } if (cancelConfirmed == 0) { this->erase(); // emit signal to make Publisher go back to minimum mode emit editingCancelled(); qDebug() << "Post cancelled"; } } dianara-v1.3.2/src/composer.h000644 000764 000764 00000005772 12573333661 015507 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef COMPOSER_H #define COMPOSER_H #include #include #include #include #include #include #include #include #include #include #include // Needed in Qt5 #include #include // For QCompleter's popup #include #include #include #include #include "globalobject.h" #include "mischelpers.h" class Composer : public QTextEdit { Q_OBJECT public: Composer(GlobalObject *globalObject, bool forPublisher, QWidget *parent = 0); ~Composer(); void erase(); void insertLink(QString url, QString title=""); void requestCompletion(QString partialNick); int getMessageLabelHeight(); void hideInfoMessage(); QPushButton *getToolsButton(); void setPlainPasteEnabled(bool state); signals: void focusReceived(); void editingFinished(); void editingCancelled(); void focusTitleRequested(); void nickInserted(QString id, QString name, QString url); public slots: void makeNormal(); void makeBold(); void makeItalic(); void makeUnderline(); void makeStrikethrough(); void makeHeader(); void makeList(); void makeTable(); void makePreformatted(); void makeQuote(); void makeLink(); void insertImage(); void insertLine(); void insertSymbol(QAction *action); void pasteAsPlaintext(); void insertCompletedNick(QModelIndex nickData); void cancelPost(); protected: virtual void focusInEvent(QFocusEvent *event); virtual void keyPressEvent(QKeyEvent *event); virtual void contextMenuEvent(QContextMenuEvent *event); virtual void insertFromMimeData(const QMimeData *source); private: QHBoxLayout *mainLayout; QLabel *startConversationLabel; QPushButton *toolsButton; QMenu *toolsMenu; QMenu *symbolsMenu; QAction *pastePlaintextAction; QMenu *customContextMenu; QCompleter *nickCompleter; QTableView *popupTableView; bool forPublisher; GlobalObject *globalObj; }; #endif // COMPOSER_H dianara-v1.3.2/src/comment.h000644 000764 000764 00000006210 12573333661 015306 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef COMMENT_H #define COMMENT_H #include #include #include #include #include #include #include #include #include #include #include "pumpcontroller.h" #include "globalobject.h" #include "mischelpers.h" #include "timestamp.h" #include "asobject.h" #include "avatarbutton.h" #include "hclabel.h" class Comment : public QFrame { Q_OBJECT public: explicit Comment(PumpController *pumpController, GlobalObject *globalObject, ASObject *commentObject, QWidget *parent = 0); ~Comment(); void updateDataFromObject(ASObject *object); void fixLikeLabelText(); void setLikesCount(QString count, QVariantList namesVariantList); void setFuzzyTimestamps(); void syncAvatarFollowState(); void setCommentContents(); void onResize(); void getPendingImages(); QString getObjectId(); void setHint(QString color=""); void setCommentDeleted(QString deletedTime); signals: void commentQuoteRequested(QString content); void commentEditRequested(QString id, QString content); public slots: void likeComment(QString clickedLink); void saveCommentSelectedText(); void quoteComment(); void editComment(); void deleteComment(); void showUrlInfo(QString url); void redrawImages(QString imageUrl); protected: virtual void leaveEvent(QEvent *event); virtual void resizeEvent(QResizeEvent *event); private: QHBoxLayout *mainLayout; QVBoxLayout *leftLayout; QVBoxLayout *rightLayout; QHBoxLayout *rightTopLayout; AvatarButton *avatarButton; QLabel *fullNameLabel; HClabel *timestampLabel; QLabel *contentLabel; HClabel *likesCountLabel; QWidget *hintWidget; QLabel *likeLabel; QLabel *quoteLabel; QLabel *editLabel; QLabel *deleteLabel; QString commentId; QString objectType; QString commentAuthorId; bool commentIsOwn; bool commentIsLiked; bool commentIsDeleted; QString createdAt; QString updatedAt; QString commentOriginalText; QStringList pendingImagesList; QString commentSelectedText; PumpController *pController; GlobalObject *globalObj; }; #endif // COMMENT_H dianara-v1.3.2/src/timestamp.h000664 000764 000764 00000002500 12451104247 015636 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef TIMESTAMP_H #define TIMESTAMP_H #include #include #include class Timestamp : public QObject { Q_OBJECT public: explicit Timestamp(QObject *parent = 0); // isoTime = ISO 8601, UTC, like "2012-02-07T01:32:02Z" static QString localTimeDate(QString isoTime); // returns "07-02-2012\n01:32:02" static QString fuzzyTime(QString isoTime); // returns "about an hour ago", etc. signals: public slots: }; #endif // TIMESTAMP_H dianara-v1.3.2/src/timestamp.cpp000664 000764 000764 00000011747 12451104251 016201 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "timestamp.h" Timestamp::Timestamp(QObject *parent) : QObject(parent) { // Creating object not required } /* * returns " 07-02-2012 - 01:32:02", adjusted to local time * * isoTime = ISO 8601, UTC, like "2012-02-07T01:32:02Z" */ QString Timestamp::localTimeDate(QString isoTime) { //qDebug() << "::localTimeDate - isoTime: " << isoTime; QDateTime dateTimeConverter = QDateTime::fromString(isoTime, Qt::ISODate).toLocalTime(); QString localTimeDateString; localTimeDateString = dateTimeConverter.date().toString(Qt::DefaultLocaleLongDate); localTimeDateString.append(", "); localTimeDateString.append(dateTimeConverter.time().toString()); return localTimeDateString; } /* * returns "about an hour ago", etc, based on local time * * isoTime = ISO 8601, UTC, like "2012-02-07T01:32:02Z" */ QString Timestamp::fuzzyTime(QString isoTime) { if (isoTime.isEmpty()) { return tr("Invalid timestamp!"); } QDateTime dateTimeConverter = QDateTime::fromString(isoTime, Qt::ISODate); int timeDifference = QDateTime::currentDateTimeUtc().toTime_t() - dateTimeConverter.toTime_t(); //qDebug() << "time difference in seconds:" << timeDifference; QString localTimeDateString = QString("ABOUT ... AGO"); // At this point, timeDifference is in SECONDS if (timeDifference < 60) // less than a minute, or FUTURE { if (timeDifference >= 0) { localTimeDateString = tr("Just now"); } else // negative difference, so timestamp is in the future { localTimeDateString = tr("In the future"); } } else { timeDifference /= 60; // convert to minutes if (timeDifference < 60) // less than an hour { if (timeDifference == 1) { localTimeDateString = tr("A minute ago"); } else { localTimeDateString = tr("%1 minutes ago").arg(timeDifference); } } else { timeDifference /= 60; // convert to hours if (timeDifference < 24) // less than a day { if (timeDifference == 1) { localTimeDateString = tr("An hour ago"); } else { localTimeDateString = tr("%1 hours ago").arg(timeDifference); } } else { timeDifference /= 24; // convert to days if (timeDifference < 30) // less than a (average) month { if (timeDifference == 1) { localTimeDateString = tr("Yesterday"); } else { localTimeDateString = tr("%1 days ago").arg(timeDifference); } } else { timeDifference /= 30; // convert to months, approx. if (timeDifference < 12) // less than a year { if (timeDifference == 1) { localTimeDateString = tr("A month ago"); } else { localTimeDateString = tr("%1 months ago").arg(timeDifference); } } else { timeDifference /= 12; if (timeDifference == 1) { localTimeDateString = tr("A year ago"); } else { localTimeDateString = tr("%1 years ago").arg(timeDifference); } } } } } } // No, I'm not particularly proud of this code.... oh, well... //qDebug() << "Posted:" << localTimeDateString; return localTimeDateString; } dianara-v1.3.2/src/contactmanager.h000664 000764 000764 00000005326 12564115056 016637 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef CONTACTMANAGER_H #define CONTACTMANAGER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pumpcontroller.h" #include "contactlist.h" #include "listsmanager.h" #include "globalobject.h" #include "siteuserslist.h" class ContactManager : public QWidget { Q_OBJECT public: ContactManager(PumpController *pumpController, GlobalObject *globalObject, QWidget *parent = 0); ~ContactManager(); void setTabLabels(); void exportContactsToFile(QString listType); signals: public slots: void setContactListsContents(QString listType, QVariantList contactList, int totalReceivedCount); void setListsListContents(QVariantList listsList); void changeFollowingCount(int difference); void refreshFollowing(); void refreshFollowers(); void exportFollowing(); void exportFollowers(); void refreshPersonLists(); void toggleFollowButton(QString currentAddress); void followContact(); protected: private: QVBoxLayout *mainLayout; QHBoxLayout *topLayout; QLabel *enterAddressLabel; QLineEdit *addressLineEdit; QPushButton *followButton; QTabWidget *tabWidget; ContactList *followingWidget; int followingCount; ContactList *followersWidget; int followersCount; ListsManager *listsManager; QScrollArea *listsScrollArea; int listsCount; SiteUsersList *siteUsersList; QPushButton *optionsButton; QMenu *optionsMenu; PumpController *pController; GlobalObject *globalObj; }; #endif // CONTACTMANAGER_H dianara-v1.3.2/src/contactlist.h000664 000764 000764 00000004275 12526465516 016211 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef CONTACTLIST_H #define CONTACTLIST_H #include #include #include #include #include #include #include #include "pumpcontroller.h" #include "globalobject.h" #include "contactcard.h" class ContactList : public QWidget { Q_OBJECT public: explicit ContactList(PumpController *pumpController, GlobalObject *globalObject, QString listType, QWidget *parent = 0); ~ContactList(); void clearListContents(); void setListContents(QVariantList contactList); QString getContactsStringForExport(); signals: void contactCountChanged(int difference); public slots: void filterList(QString filterText); void addSingleContact(ASPerson *contact); void removeSingleContact(ASPerson *contact); private: PumpController *pController; GlobalObject *globalObj; QVBoxLayout *mainLayout; QAction *goToFilterAction; QHBoxLayout *filterLayout; QLabel *filterIcon; QLineEdit *filterLineEdit; QPushButton *removeFilterButton; QVBoxLayout *contactsLayout; QWidget *contactsWidget; QScrollArea *contactsScrollArea; QList contactsInList; QString contactsStringForExport; bool isFollowing; }; #endif // CONTACTLIST_H dianara-v1.3.2/src/contactcard.h000664 000764 000764 00000004670 12523415324 016133 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef CONTACTCARD_H #define CONTACTCARD_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mischelpers.h" #include "pumpcontroller.h" #include "globalobject.h" #include "asperson.h" #include "timestamp.h" class ContactCard : public QFrame { Q_OBJECT public: ContactCard(PumpController *pumpController, GlobalObject *globalObject, ASPerson *asPerson, QWidget *parent = 0); ~ContactCard(); void setButtonToFollow(); void setButtonToUnfollow(); bool setAvatar(QString avatarFilename); QString getNameAndIdString(); QString getId(); signals: public slots: void followContact(); void unfollowContact(); void openProfileInBrowser(); void setMessagingModeForContact(); void browseContactPosts(); void redrawAvatar(QString avatarUrl, QString avatarFilename); private: QHBoxLayout *mainLayout; QVBoxLayout *rightLayout; QLabel *avatarLabel; QLabel *userInfoLabel; QPushButton *followButton; QPushButton *optionsButton; QMenu *optionsMenu; QAction *openProfileAction; QAction *sendMessageAction; QAction *browsePostsAction; QMenu *addToListMenu; PumpController *pController; GlobalObject *globalObj; QString contactName; QString contactId; QString contactUrl; QString contactAvatarUrl; QString contactOutbox; }; #endif // CONTACTCARD_H dianara-v1.3.2/src/contactcard.cpp000644 000764 000764 00000026113 12573333661 016470 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "contactcard.h" ContactCard::ContactCard(PumpController *pumpController, GlobalObject *globalObject, ASPerson *asPerson, QWidget *parent) : QFrame(parent) { this->pController = pumpController; this->globalObj = globalObject; asPerson->setParent(this); // reparent the person object this->setFrameStyle(QFrame::Box | QFrame::Raised); this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); // Left widget, user avatar avatarLabel = new QLabel(this); this->contactAvatarUrl = asPerson->getAvatar(); // Get local file name, which is stored in base64 hash form QString avatarFilename = MiscHelpers::getCachedAvatarFilename(contactAvatarUrl); // Load avatar if already cached bool validAvatar = this->setAvatar(avatarFilename); if (!validAvatar && !contactAvatarUrl.isEmpty()) { connect(pController, SIGNAL(avatarStored(QString,QString)), this, SLOT(redrawAvatar(QString,QString))); // Download avatar for next time pController->enqueueAvatarForDownload(contactAvatarUrl); } // Center widget, user info QFont nameFont; nameFont.setBold(true); nameFont.setUnderline(true); this->contactName = asPerson->getName(); this->contactId = asPerson->getId(); this->contactUrl = asPerson->getUrl(); this->contactOutbox = asPerson->getOutboxLink(); QString userInfoString = QString("%1
").arg(contactName); userInfoString.append(QString("%1
").arg(contactId)); userInfoString.append("" + tr("Hometown") + QString(": %1").arg(asPerson->getHometown()) + ""); QString userCreationDate = asPerson->getCreatedAt(); if (!userCreationDate.isEmpty()) { userInfoString.append("
" + tr("Joined: %1") .arg(Timestamp::fuzzyTime(userCreationDate)) + ""); } else { userCreationDate = asPerson->getupdatedAt(); userInfoString.append("
" + tr("Updated: %1") .arg(Timestamp::fuzzyTime(userCreationDate)) + ""); } userInfoLabel = new QLabel(this); userInfoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); userInfoLabel->setText(userInfoString); userInfoLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); userInfoLabel->setWordWrap(true); userInfoLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); // Bio as tooltip for the whole contact card QString contactBio = asPerson->getBio().replace("\n", "
"); if (!contactBio.isEmpty()) { this->setToolTip("" // Make it rich text, so that it gets wordwrapped + tr("Bio for %1", "Abbreviation for Biography, " "but you can use the full word; " "%1=contact name") .arg(contactName) + "" "

" + contactBio); } else { if (contactName.isEmpty()) { this->setToolTip(tr("This user doesn't have a biography")); } else { this->setToolTip(tr("No biography for %1", "%1=contact name").arg(contactName)); } } // Right column, buttons followButton = new QPushButton("*follow*", this); followButton->setFlat(true); if (asPerson->isFollowed()) { this->setButtonToUnfollow(); } else { this->setButtonToFollow(); } openProfileAction = new QAction(QIcon::fromTheme("internet-web-browser", QIcon(":/images/button-download.png")), tr("Open Profile in Web Browser"), this); connect(openProfileAction, SIGNAL(triggered()), this, SLOT(openProfileInBrowser())); if (this->contactUrl.isEmpty()) // Disable if there is no URL { openProfileAction->setDisabled(true); } sendMessageAction = new QAction(QIcon::fromTheme("document-edit", QIcon(":/images/button-edit.png")), tr("Send Message"), this); connect(sendMessageAction, SIGNAL(triggered()), this, SLOT(setMessagingModeForContact())); browsePostsAction = new QAction(QIcon::fromTheme("edit-find", QIcon(":/images/menu-find.png")), tr("Browse Messages"), this); connect(browsePostsAction, SIGNAL(triggered()), this, SLOT(browseContactPosts())); addToListMenu = new QMenu(tr("In Lists..."), this); addToListMenu->setIcon(QIcon::fromTheme("format-list-unordered")); addToListMenu->addAction("fake list 1")->setCheckable(true); // FIXME... addToListMenu->addAction("fake list 2")->setCheckable(true); addToListMenu->addAction("fake list 3")->setCheckable(true); optionsMenu = new QMenu("*options*", this); optionsMenu->addAction(openProfileAction); optionsMenu->addAction(sendMessageAction); optionsMenu->addAction(browsePostsAction); browsePostsAction->setEnabled(pController->urlIsInOurHost(this->contactOutbox)); //optionsMenu->addMenu(addToListMenu); // Don't include it for now, until 1.4 /FIXME optionsButton = new QPushButton(QIcon::fromTheme("user-properties", QIcon(":/images/no-avatar.png")), tr("User Options"), this); optionsButton->setFlat(true); optionsButton->setMenu(optionsMenu); // Layout rightLayout = new QVBoxLayout(); rightLayout->setAlignment(Qt::AlignTop); rightLayout->addWidget(followButton); rightLayout->addWidget(optionsButton); mainLayout = new QHBoxLayout(); mainLayout->addWidget(avatarLabel, 0, Qt::AlignTop); mainLayout->addWidget(userInfoLabel, 1); mainLayout->addLayout(rightLayout); this->setLayout(mainLayout); qDebug() << "ContactCard created" << this->contactId; } ContactCard::~ContactCard() { qDebug() << "ContactCard destroyed" << this->contactId; } void ContactCard::setButtonToFollow() { followButton->setIcon(QIcon::fromTheme("list-add", QIcon(":/images/list-add.png"))); followButton->setText(tr("Follow")); connect(followButton, SIGNAL(clicked()), this, SLOT(followContact())); disconnect(followButton, SIGNAL(clicked()), this, SLOT(unfollowContact())); } void ContactCard::setButtonToUnfollow() { followButton->setIcon(QIcon::fromTheme("list-remove", QIcon(":/images/list-remove.png"))); followButton->setText(tr("Stop Following")); connect(followButton, SIGNAL(clicked()), this, SLOT(unfollowContact())); disconnect(followButton, SIGNAL(clicked()), this, SLOT(followContact())); } bool ContactCard::setAvatar(QString avatarFilename) { bool validAvatar = false; QPixmap avatarPixmap = QPixmap(avatarFilename) .scaledToWidth(64, Qt::SmoothTransformation); if (!avatarPixmap.isNull()) { avatarLabel->setPixmap(avatarPixmap); validAvatar = true; qDebug() << "ContactCard: Using cached avatar"; } if (!validAvatar) { // Placeholder image avatarLabel->setPixmap(QIcon::fromTheme("user-identity", QIcon(":/images/no-avatar.png")).pixmap(64, 64)); qDebug() << "ContactCard: Using placeholder"; } return validAvatar; } QString ContactCard::getNameAndIdString() { return QString("%1 <%2>").arg(contactName).arg(contactId); } QString ContactCard::getId() { return contactId; } /**************************************************************************/ /******************************** SLOTS ***********************************/ /**************************************************************************/ void ContactCard::followContact() { this->pController->followContact(this->contactId); this->setButtonToUnfollow(); } void ContactCard::unfollowContact() { int confirmation = QMessageBox::question(this, tr("Stop following?"), tr("Are you sure you want to stop following %1?") .arg(this->contactId), tr("&Yes, stop following"), tr("&No"), "", 1, 1); if (confirmation == 0) { this->pController->unfollowContact(this->contactId); this->setButtonToFollow(); } } void ContactCard::openProfileInBrowser() { QDesktopServices::openUrl(this->contactUrl); } void ContactCard::setMessagingModeForContact() { this->globalObj->createMessageForContact(this->contactId, this->contactName, this->contactUrl); } void ContactCard::browseContactPosts() { const QPixmap contactPixmap(this->avatarLabel->pixmap()->copy()); this->globalObj->browseUserMessages(this->contactId, this->contactName, QIcon(contactPixmap), this->contactOutbox + "/major"); // TMP! } /* * Redraw contact's avatar after it's been downloaded and stored * */ void ContactCard::redrawAvatar(QString avatarUrl, QString avatarFilename) { if (avatarUrl == this->contactAvatarUrl) { disconnect(pController, SIGNAL(avatarStored(QString,QString)), this, SLOT(redrawAvatar(QString,QString))); this->setAvatar(avatarFilename); } } dianara-v1.3.2/src/mischelpers.h000664 000764 000764 00000004751 12521025432 016157 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef MISCHELPERS_H #define MISCHELPERS_H #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #endif #include #include class MiscHelpers : public QObject { Q_OBJECT public: explicit MiscHelpers(QObject *parent = 0); static QString getCachedAvatarFilename(QString url); static QString getCachedImageFilename(QString url); static QString getSuggestedFilename(QString authorId, QString postType, QString postTitle, QString fileUrl); static QString getFileMimeType(QString fileUri); static int getImageWidth(QString fileURI); static bool isImageAnimated(QString fileUri); static QString fixLongName(QString name); static QString fileSizeString(QString fileURI); static QStringList htmlWithReplacedImages(QString originalHtml, int postWidth); static QString cleanupHtml(QString originalHtml); static QString htmlWithoutLinks(QString originalHtml); static QString htmlToPlainText(QString html, int charLimit=0); static QString quotedText(QString author, QString content); static QString elidedText(QString text, int charLimit); static QString mediaHtmlBase(QString postType, QString attachmentFilename, QString tooltipMessage, QString belowMessage, int imageWidth = -1); }; #endif // MISCHELPERS_H dianara-v1.3.2/src/mischelpers.cpp000664 000764 000764 00000035476 12613340107 016523 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "mischelpers.h" MiscHelpers::MiscHelpers(QObject *parent) : QObject(parent) { // Creating object not required, all static functions } QString MiscHelpers::getCachedAvatarFilename(QString url) { QString localFilename; if (!url.isEmpty()) { #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) localFilename = QDesktopServices::storageLocation(QDesktopServices::DataLocation); #else localFilename = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first(); #endif localFilename.append("/avatars/"); localFilename.append(url.trimmed().toUtf8().toBase64()); QString fileExtension = url; fileExtension.remove(QRegExp(".*\\.")); // remove all but the extension localFilename.append("."); localFilename.append(fileExtension); } return localFilename; } QString MiscHelpers::getCachedImageFilename(QString url) { QString localFilename; if (!url.isEmpty()) { url = url.trimmed(); if (url.startsWith("http://")) { url.remove(0, 7); // remove http:// } if (url.startsWith("https://")) { url.remove(0, 8); // remove https:// } // Convert to percentEncoding before, to avoid having "/" and such in the filename QByteArray base64url = url.toUtf8().toPercentEncoding().toBase64(); base64url.truncate(255); // Limit filename length! Just in case the URL is VERY long #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) localFilename = QDesktopServices::storageLocation(QDesktopServices::DataLocation); #else localFilename = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first(); #endif localFilename.append("/images/"); localFilename.append(base64url); } return localFilename; } /* * Generate suggested filename for an attachment, * based on author ID, original extension, etc. * */ QString MiscHelpers::getSuggestedFilename(QString authorId, QString postType, QString postTitle, QString fileUrl) { // Get original filename in server QString originalFilename = fileUrl.split("/").last(); QString suggestedFilename = authorId; suggestedFilename.append("_" + postType); if (!postTitle.trimmed().isEmpty()) { suggestedFilename.append("_" + postTitle); } suggestedFilename.replace("@", "-"); // Avoid certain chars from author ID suggestedFilename.replace(".", "-"); suggestedFilename.replace("/", "-"); suggestedFilename.remove("?"); suggestedFilename.remove("!"); suggestedFilename.remove("*"); suggestedFilename.remove(":"); suggestedFilename.remove(";"); suggestedFilename.remove("\""); suggestedFilename.replace(" ", "_"); // Avoid spaces in filename suggestedFilename.append("_" + originalFilename); // Something like A53r2w.png // Extension will be .bin for generic files, at the moment return suggestedFilename; } /* * Return MIME content type, like image/png, audio/ogg, etc. * * using libmagic * */ QString MiscHelpers::getFileMimeType(QString fileUri) { qDebug() << "getFileMimeType() file:" << fileUri; magic_t magicCookie = magic_open(MAGIC_MIME_TYPE); if (magicCookie == NULL) { qDebug() << "libmagic init error!"; return QString(); } if (magic_load(magicCookie, NULL) != 0) { qDebug() << "magic_load() error! No system-wide libmagic DB?"; // Try loading from a subdirectory instead (in mswindows, maybe osx) if (magic_load(magicCookie, "plugins/magic") != 0) { qDebug() << "magic_load() error; Can't load plugins/magic.mgc"; magic_close(magicCookie); return QString(); } } const char *magicMimeString; magicMimeString = magic_file(magicCookie, fileUri.toLocal8Bit()); QString mimeType = QString(magicMimeString); qDebug() << "File MIME type:" << mimeType; magic_close(magicCookie); return mimeType; } /* * Return width of an image * */ int MiscHelpers::getImageWidth(QString fileURI) { QImageReader imageReader(fileURI); return imageReader.size().width(); } bool MiscHelpers::isImageAnimated(QString fileUri) { //qDebug() << "QMovie::supportedFormats()" << QMovie::supportedFormats(); bool isAnimated = false; QImageReader imageReader(fileUri); if (imageReader.supportsAnimation()) { QMovie movie; movie.setFileName(fileUri); qDebug() << "Image format SUPPORTS animation; Frames:" << movie.frameCount(); if (movie.frameCount() > 1) // FIXME: doesn't work with .MNG, so it returns 0 { qDebug() << "Image IS animated."; isAnimated = true; } } return isAnimated; } QString MiscHelpers::fixLongName(QString name) { // very TMP optimization of LOOONG names / FIXME if (name.length() > 16) { name.replace("@", "@ "); name.replace(".", ". "); } return name; } /* * Return a pretty string with the size of a file, like * "33 KiB", "512 bytes" or "3,2 MiB" * */ QString MiscHelpers::fileSizeString(QString fileURI) { QFileInfo fileInfo(fileURI); double fileSize = fileInfo.size(); QString sizeUnit = tr("bytes"); if (fileSize > 1024) // if > 1024 bytes, transform to KiB { fileSize /= 1024.0; sizeUnit = "KiB"; } if (fileSize > 1024) // if > 1024 KiB, transform to MiB { fileSize /= 1024.0; sizeUnit = "MiB"; } // Return with 0 padding and 2 decimal precision return QString("%1 %2").arg(QLocale::system().toString(fileSize, 'f', 2)) .arg(sizeUnit); } /* * Parse a string of HTML and replace the URL in each tag with * the corresponding locally cached filename. * * Return also the string list of the URL's to download * */ QStringList MiscHelpers::htmlWithReplacedImages(QString originalHtml, int postWidth) { // if no tags..."; return QStringList(originalHtml); } //qDebug() << "MiscHelpers::htmlWithReplacedImages(); HTML contains some tags..."; QString newHtml = originalHtml; newHtml.remove("\n"); // Remove in case misbehaving applications added any QStringList imageList; QString imgSrc; QRegExp regExp("\\"); regExp.setMinimal(true); int matchedLength = 0; int stringPos = 0; while (matchedLength != -1) { stringPos = regExp.indexIn(newHtml, stringPos); matchedLength = regExp.matchedLength(); //qDebug() << "#######\n\nregExp match = " << regExp.cap(0); //qDebug() << "Groups:" << regExp.cap(1) << " // " << regExp.cap(2) // << " // " << regExp.cap(3) << " // " << regExp.cap(4) // << " // " << regExp.cap(5); //qDebug() << "Matched length is:" << matchedLength; imgSrc = regExp.cap(3); if (!imgSrc.isEmpty()) // if not an empty string, add to the list, and replace HTML { // If the url had parameters, they _might_ have & in place of "&" imgSrc.replace("&", "&"); // Put them back // Check if http part (scheme) is missing from URL, and add it if (!imgSrc.startsWith("http")) { imgSrc.prepend("http:"); } // Add URL to list imageList.append(imgSrc); QString cachedImageFilename = getCachedImageFilename(imgSrc); int imageWidth = getImageWidth(cachedImageFilename); // if width is bigger than the post, make it smaller to fit if (imageWidth > postWidth - 32) { // Some margins, to account for a scrollbar or a tab space before the image imageWidth = postWidth - 32; } newHtml.replace(stringPos, matchedLength, ""); } stringPos += matchedLength; // FIXME: error control } imageList.prepend(newHtml); // The modified HTML goes before the image list //qDebug() << "Returned HTML and images:\n" << imageList << "\n#################"; return imageList; } /* * Basic cleanup of HTML stuff * */ QString MiscHelpers::cleanupHtml(QString originalHtml) { QString cleanHtml = originalHtml; cleanHtml.replace("\n", " "); // Remove line breaks, as that results in server error 500 QRegExp doctypeRE(""); doctypeRE.setMinimal(true); cleanHtml.remove(doctypeRE); QRegExp headRE(".*"); headRE.setMinimal(true); cleanHtml.remove(headRE); QRegExp bodyRE(""); bodyRE.setMinimal(true); cleanHtml.remove(bodyRE); //////////////////////////////////////// Remove from links QRegExp linkStyleRE(".*"); linkStyleRE.setMinimal(true); QRegExp spanRE("(.*)"); // FIXME: remove ONLY color info spanRE.setMinimal(true); int pos = 0; while ((pos = linkStyleRE.indexIn(cleanHtml, pos)) != -1) { int removedTextOffset = 0; if (spanRE.indexIn(cleanHtml, pos) != -1) { // Replace the whole span tag by just what was inside cleanHtml.replace(spanRE.cap(0), spanRE.cap(1)); //qDebug() << "spanRE capture: " << spanRE.capturedTexts(); removedTextOffset = spanRE.cap(0).length() - spanRE.cap(1).length(); } pos += linkStyleRE.matchedLength() - removedTextOffset; } ////////////////////////////////////// Remove style from
      and
    1. QRegExp ulStyleRE("
        "); ulStyleRE.setMinimal(true); cleanHtml.replace(ulStyleRE, "
          "); QRegExp olStyleRE("
            "); olStyleRE.setMinimal(true); cleanHtml.replace(olStyleRE, "
              "); QRegExp liStyleRE("
            1. "); liStyleRE.setMinimal(true); cleanHtml.replace(liStyleRE, "
            2. "); // FIXME: Maybe try to remove background colors from

              elements cleanHtml.remove(""); return cleanHtml.trimmed(); } /* * Remove only and from a HTML text * */ QString MiscHelpers::htmlWithoutLinks(QString originalHtml) { QString cleanHtml = originalHtml; QRegExp linksRE(""); linksRE.setMinimal(true); linksRE.setCaseSensitivity(Qt::CaseInsensitive); cleanHtml.remove(linksRE); cleanHtml.remove("", Qt::CaseInsensitive); return cleanHtml; } QString MiscHelpers::htmlToPlainText(QString html, int charLimit) { QTextDocument textDocument; textDocument.setHtml(html); QString plainText = textDocument.toPlainText().trimmed(); plainText.replace("\n", " "); plainText = plainText.trimmed(); if (charLimit != 0 && plainText.length() > charLimit) { plainText = plainText.left(charLimit).trimmed() + "..."; } return plainText; } /* * Return some HTML with a blockquote, quote symbols, etc. * * */ QString MiscHelpers::quotedText(QString author, QString content) { QTextDocument textDocument; textDocument.setHtml(content); content = textDocument.toPlainText().trimmed(); content.replace("<", "<"); // back to HTML entities content.replace(">", ">"); content.replace("\n", "
              "); // Important to replace this AFTER < and > QString quoteHtml = ">> "+ author + ":" // >> + name "

              " "“" + content + "”" "

              "; return quoteHtml; } /* * Limit length of a string to charLimit chars, * removing characters from the middle * */ QString MiscHelpers::elidedText(QString text, int charLimit) { QString shortText = text; if (text.length() > charLimit) { charLimit -= 5; shortText = text.left(charLimit / 2); shortText.append(" ... "); shortText.append(text.right(charLimit / 2)); } return shortText; } /* * Generate the first part of the HTML of a post * that includes media attachments * */ QString MiscHelpers::mediaHtmlBase(QString postType, QString attachmentFilename, QString tooltipMessage, QString belowMessage, int imageWidth) { QString html = "" // First row, with gradient "" ""); // Second row, to add a message related to the image or attachment html.append("" "
              " ""); } else { html.append("=\"attachment:/\" >"); } html.append("" "
              " + belowMessage + "
              " "

              "); return html; } dianara-v1.3.2/src/minorfeed.h000664 000764 000764 00000006410 12512745767 015627 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef MINORFEED_H #define MINORFEED_H #include #include #include #include #include #include #include "pumpcontroller.h" #include "globalobject.h" #include "mischelpers.h" #include "minorfeeditem.h" #include "asactivity.h" #include "filterchecker.h" class MinorFeed : public QFrame { Q_OBJECT public: explicit MinorFeed(PumpController::requestTypes minorFeedType, PumpController *pumpController, GlobalObject *globalObject, FilterChecker *filterChecker, QWidget *parent = 0); ~MinorFeed(); void clearContents(); void removeOldItems(int minimumToKeep); void insertSeparator(int position); void markAllAsRead(); void updateFuzzyTimestamps(); void syncActivityWithTimelines(ASActivity *activity); signals: void newItemsCountChanged(int itemCount, int highlightedCount); void newItemsReceived(PumpController::requestTypes feedType, int itemCount, int highlightedCount, int filteredCount, int pendingForNextUpdate); void objectUpdated(ASObject *object); void objectLiked(QString objectId, QString objectType, QString actorId, QString actorName, QString actorUrl); void objectUnliked(QString objectId, QString objectType, QString actorId); void objectReplyAdded(ASObject *object); void objectDeleted(ASObject *object); public slots: void updateFeed(); void getMoreActivities(); void setFeedContents(QVariantList activitiesList, QString previous, QString next, int totalItemCount); void decreaseNewItemsCount(bool wasHighlighted); void updateAvatarFollowStates(); private: QString prevLink; QString nextLink; int fullFeedItemCount; int pendingToReceiveNextTime; PumpController::requestTypes feedType; int feedBatchItemCount; bool firstLoad; bool gettingNew; QString previousNewestActivityId; QList itemsInFeed; int unreadItemsCount; int highlightedItemsCount; QVBoxLayout *mainLayout; QVBoxLayout *itemsLayout; QPushButton *getPendingButton; QPushButton *getOlderButton; QFrame *separatorFrame; PumpController *pController; GlobalObject *globalObj; FilterChecker *fChecker; }; #endif // MINORFEED_H dianara-v1.3.2/src/pumpcontroller.cpp000644 000764 000764 00000331650 12613157547 017277 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "pumpcontroller.h" PumpController::PumpController(QObject *parent) : QObject(parent) { this->userAgentString = "Dianara/1.3.2"; this->serverScheme = "https://"; // Default, unless --nohttps is used this->postsPerPageMain = 20; this->postsPerPageOther = 10; this->proxyUsesAuth = false; this->ignoreSslErrors = false; this->ignoreSslInImages = false; this->silentFollows = false; this->silentListsHandling = false; this->silentLikes = false; qDebug() << "PumpController: about to initialize QOAuth.\n" << "** If you built Dianara with Qt 5, you'll probably get a crash!"; qoauth = new QOAuth::Interface(this); qoauth->setRequestTimeout(60000); // 1 minute timeout QSettings settings; this->clientID = settings.value("clientID").toString(); this->clientSecret = settings.value("clientSecret").toString(); qoauth->setConsumerKey(clientID.toLocal8Bit()); qoauth->setConsumerSecret(clientSecret.toLocal8Bit()); this->isApplicationAuthorized = settings.value("isApplicationAuthorized", false).toBool(); if (isApplicationAuthorized) { qDebug() << "Dianara is already authorized for user ID:" << settings.value("userID").toString(); this->token = settings.value("token").toString().toLocal8Bit(); this->tokenSecret = settings.value("tokenSecret").toString().toLocal8Bit(); qDebug() << "Using token" << token; qDebug() << "And token secret" << tokenSecret.left(5) + "********** (hidden)"; } connect(&nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))); // Handle SSL errors; ignore for images if option is set, // or for everything if --ignoresslerrors is used connect(&nam, SIGNAL(sslErrors(QNetworkReply*,QList)), this, SLOT(sslErrorsHandler(QNetworkReply*,QList))); this->userFollowingCount = 0; this->userFollowersCount = 0; this->initialDataStep = 0; this->initialDataAttempts = 0; initialDataTimer = new QTimer(this); initialDataTimer->setSingleShot(false); // Triggered constantly until stopped connect(initialDataTimer, SIGNAL(timeout()), this, SLOT(getInitialData())); qDebug() << "PumpController created"; } PumpController::~PumpController() { qDebug() << "PumpController destroyed"; } void PumpController::setProxyConfig(QNetworkProxy::ProxyType proxyType, QString hostname, int port, bool useAuth, QString user, QString password) { QNetworkProxy proxy; proxy.setType(proxyType); proxy.setHostName(hostname); proxy.setPort(port); if (useAuth) { proxy.setUser(user); proxy.setPassword(password); // If using auth, and proxy type is set if (proxyType != QNetworkProxy::NoProxy) { this->proxyUsesAuth = true; } } nam.setProxy(proxy); qoauth->networkAccessManager()->setProxy(proxy); qDebug() << "Proxy config applied:" << hostname << port << user; } bool PumpController::needsProxyPassword() { if (this->proxyUsesAuth && this->nam.proxy().password().isEmpty()) { return true; } return false; } void PumpController::setProxyPassword(QString password) { QNetworkProxy proxy = this->nam.proxy(); proxy.setPassword(password); this->nam.setProxy(proxy); this->qoauth->networkAccessManager()->setProxy(proxy); } void PumpController::updateApiUrls() { this->apiBaseUrl = this->serverScheme + this->serverURL + "/api/user/" + this->userName; qDebug() << "Base API URL is:" << this->apiBaseUrl; this->apiFeedUrl = this->apiBaseUrl + "/feed"; } void PumpController::setPostsPerPageMain(int ppp) { this->postsPerPageMain = ppp; qDebug() << "PumpController: setting postsPerPage (main) to" << this->postsPerPageMain; } void PumpController::setPostsPerPageOther(int ppp) { this->postsPerPageOther = ppp; qDebug() << "PumpController: setting postsPerPage (other) to" << this->postsPerPageOther; } /* * Set new user ID (user@domain.tld) and clear OAuth-related tokens/secrets * * */ void PumpController::setNewUserId(QString userID) { this->userId = userID; QStringList splittedUserID = this->userId.split("@"); this->userName = splittedUserID.first(); // get username, before @ this->serverURL = splittedUserID.last(); // get URL, after @ qDebug() << "Server URL to connect:" << serverURL << "; username:" << userName; this->clientID.clear(); this->clientSecret.clear(); this->token.clear(); this->tokenSecret.clear(); this->postsEverSeen.clear(); this->isApplicationAuthorized = false; emit this->authorizationStatusChanged(isApplicationAuthorized); } /* * Get "pumpserver.org" and "user" from "user@pumpserver.org", * set OAuth token from Account dialog * */ void PumpController::setUserCredentials(QString userID) { this->initialDataTimer->stop(); // Just in case it was running before this->userId = userID; QStringList splittedUserID = this->userId.split("@"); this->userName = splittedUserID.first(); this->serverURL = splittedUserID.last(); qDebug() << "New userID is:" << this->userId; this->updateApiUrls(); emit this->authorizationStatusChanged(isApplicationAuthorized); this->haveProfile = false; this->haveFollowing = false; this->haveFollowers = false; this->havePersonLists = false; this->haveMainTL = false; this->haveDirectTL = false; this->haveActivityTL = false; this->haveFavoritesTL = false; this->haveMainMF = false; this->haveDirectMF = false; this->haveActivityMF = false; this->initialDataStep = 0; this->initialDataAttempts = 0; // This will call getUserProfile(), getContactList(), several getFeed()... if (isApplicationAuthorized) { this->initialDataTimer->start(2000); // start 2 seconds after setting the ID // (mainly on program startup) emit logMessage(tr("Authorized to use account %1. Getting initial data.") .arg(this->userId)); } else { emit logMessage(tr("There is no authorized account.")); } } QString PumpController::currentUserId() { return this->userId; } QString PumpController::currentUsername() { return this->userName; } QString PumpController::currentServerScheme() { return this->serverScheme; } QString PumpController::currentServerUrl() { return this->serverURL; } QString PumpController::currentFollowersUrl() { return this->userFollowersURL; } int PumpController::currentFollowersCount() { return this->userFollowersCount; } int PumpController::currentFollowingCount() { return this->userFollowingCount; } bool PumpController::currentlyAuthorized() { return this->isApplicationAuthorized; } /* * Get any user's profile (not only our own) * * GET https://pumpserver.example/api/user/username * */ void PumpController::getUserProfile(QString userID) { QStringList splittedUserID = userID.split("@"); QString url = this->serverScheme + splittedUserID.last() + "/api/user/" + splittedUserID.first(); QNetworkRequest userProfileRequest = this->prepareRequest(url, QOAuth::GET, UserProfileRequest); nam.get(userProfileRequest); qDebug() << "Requested user profile:" << userProfileRequest.url().toString(); } /* * Update user's profile * */ void PumpController::updateUserProfile(QString avatarUrl, QString fullName, QString hometown, QString bio) { QString url = this->apiBaseUrl + "/profile"; QNetworkRequest updateProfileRequest = this->prepareRequest(url, QOAuth::PUT, UpdateProfileRequest); QVariantMap jsonVariantImage; jsonVariantImage.insert("url", avatarUrl); jsonVariantImage.insert("width", 90); // FIXME: don't hardcode this jsonVariantImage.insert("height", 90); // get values from actual pixmap QVariantMap jsonVariantLocation; jsonVariantLocation.insert("objectType", "place"); jsonVariantLocation.insert("displayName", hometown); QVariantMap jsonVariant; jsonVariant.insert("objectType", "person"); if (!avatarUrl.isEmpty()) // Only add image object if a new image was uploaded { jsonVariant.insert("image", jsonVariantImage); } jsonVariant.insert("displayName", fullName); jsonVariant.insert("location", jsonVariantLocation); jsonVariant.insert("summary", bio); QByteArray data = this->prepareJSON(jsonVariant); nam.put(updateProfileRequest, data); qDebug() << "Updating user profile" << fullName << hometown; } /* * Update user's e-mail, used for notifications and such. * This might change in the future. * */ void PumpController::updateUserEmail(QString newEmail, QString password) { QNetworkRequest updateEmailRequest = this->prepareRequest(apiBaseUrl, QOAuth::PUT, UpdateEmailRequest); QVariantMap jsonEmailVariant; jsonEmailVariant.insert("email", newEmail); // Changing the e-mail requires providing username (inmutable) and password jsonEmailVariant.insert("nickname", this->userName); jsonEmailVariant.insert("password", password); QByteArray data = this->prepareJSON(jsonEmailVariant); nam.put(updateEmailRequest, data); } /* * Add an avatar URL to the queue of pending * */ void PumpController::enqueueAvatarForDownload(QString url) { if (QFile::exists(MiscHelpers::getCachedAvatarFilename(url)) || pendingAvatarsList.contains(url)) { qDebug() << "PumpController() Using cached avatar, or it is pending download..."; } else { this->pendingAvatarsList.append(url); this->getAvatar(url); qDebug() << "PumpController() Avatar not cached, downloading" << url; } } /* * Add the URL of an image to the queue of pending-download * * connect() signal/slot if necessary to refresh when done * */ void PumpController::enqueueImageForDownload(QString url) { if (QFile::exists(MiscHelpers::getCachedImageFilename(url)) || pendingImagesList.contains(url)) { qDebug() << "PumpController::enqueueImageForDownload(), " "Using cached image, or requested image is pending download..."; } else { this->pendingImagesList.append(url); this->getImage(url); qDebug() << "PumpController::enqueueImageForDownload(), " "image not cached, downloading" << url; } } void PumpController::getAvatar(QString avatarUrl) { if (avatarUrl.isEmpty()) { return; } qDebug() << "Getting avatar" << avatarUrl; QNetworkRequest avatarRequest(QUrl((const QString)avatarUrl)); avatarRequest.setRawHeader("User-Agent", userAgentString); avatarRequest.setAttribute(QNetworkRequest::User, QVariant(AvatarRequest)); nam.get(avatarRequest); } void PumpController::getImage(QString imageUrl) { if (imageUrl.isEmpty()) { return; } QNetworkRequest imageRequest = this->prepareRequest(imageUrl, QOAuth::GET, ImageRequest); nam.get(imageRequest); qDebug() << "getImage() imageRequest sent"; } QNetworkReply *PumpController::getMedia(QString mediaUrl) { QNetworkRequest mediaRequest = this->prepareRequest(mediaUrl, QOAuth::GET, MediaRequest); qDebug() << "getMedia() sending mediaRequest for:" << mediaUrl; return nam.get(mediaRequest); } void PumpController::notifyAvatarStored(QString avatarUrl, QString avatarFilename) { pendingAvatarsList.removeAll(avatarUrl); emit avatarStored(avatarUrl, avatarFilename); } void PumpController::notifyImageStored(QString imageUrl) { pendingImagesList.removeAll(imageUrl); emit imageStored(imageUrl); } void PumpController::notifyImageFailed(QString imageUrl) { pendingImagesList.removeAll(imageUrl); emit imageFailed(imageUrl); } /* * GET https://pumpserver.example/api/user/username/following or /followers * */ void PumpController::getContactList(QString listType, int offset) { qDebug() << "Getting contact list, type" << listType << "; offset:" << offset; QString url = this->apiBaseUrl + "/" + listType; QOAuth::ParamMap paramMap; paramMap.insert("count", "200"); // 200 each time (max allowed by API) paramMap.insert("offset", QString("%1").arg(offset).toLocal8Bit()); QNetworkRequest contactListRequest; if (listType == "following") { contactListRequest = this->prepareRequest(url, QOAuth::GET, FollowingListRequest, paramMap); if (offset == 0) { totalReceivedFollowing = 0; followingIdList.clear(); this->showStatusMessageAndLogIt(tr("Getting list of 'Following'...")); } } else { contactListRequest = this->prepareRequest(url, QOAuth::GET, FollowersListRequest, paramMap); if (offset == 0) { totalReceivedFollowers = 0; } this->showStatusMessageAndLogIt(tr("Getting list of 'Followers'...")); } nam.get(contactListRequest); } void PumpController::getSiteUserList() { QString url = this->serverScheme + this->serverURL + "/api/users"; QOAuth::ParamMap paramMap; paramMap.insert("count", "50"); // 50 users instead of the server default QNetworkRequest siteUsersRequest = this->prepareRequest(url, QOAuth::GET, SiteUserListRequest, paramMap); emit currentJobChanged(tr("Getting site users for %1...", "%1 is a server name") .arg(this->serverURL)); nam.get(siteUsersRequest); } /* * Check if a userID is in the "following" list * */ bool PumpController::userInFollowing(QString contactId) { if (followingIdList.contains(contactId)) { return true; } else { return false; } } void PumpController::updateInternalFollowingIdList(QStringList idList) { this->followingIdList.append(idList); } void PumpController::removeFromInternalFollowingList(QString id) { this->followingIdList.removeAll(id); } /* * GET https://pumpserver.example/api/user/username/lists/person * */ void PumpController::getListsList() { qDebug() << "Getting list of lists"; QString url = this->apiBaseUrl + "/lists/person"; QOAuth::ParamMap paramMap; paramMap.insert("count", "200"); // Get up to 200 lists QNetworkRequest listsListRequest = this->prepareRequest(url, QOAuth::GET, ListsListRequest, paramMap); emit currentJobChanged(tr("Getting list of person lists...")); nam.get(listsListRequest); } /* * Create a person list * */ void PumpController::createPersonList(QString name, QString description) { qDebug() << "PumpController() creating person list:" << name; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, CreatePersonListRequest); QVariantList jsonVariantObjectTypes; jsonVariantObjectTypes << "person"; QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "collection"); jsonVariantObject.insert("objectTypes", jsonVariantObjectTypes); jsonVariantObject.insert("displayName", name); jsonVariantObject.insert("content", description); QVariantMap jsonVariant; jsonVariant.insert("verb", "create"); jsonVariant.insert("object", jsonVariantObject); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; emit currentJobChanged(tr("Creating person list...")); nam.post(postRequest, data); } void PumpController::deletePersonList(QString id) { qDebug() << "PumpController::deletePersonList() deleting list" << id; QNetworkRequest deleteRequest = this->prepareRequest(id, QOAuth::DELETE, DeletePersonListRequest); emit currentJobChanged(tr("Deleting person list...")); nam.deleteResource(deleteRequest); } void PumpController::getPersonList(QString url) { qDebug() << "Getting a person list:" << url; QOAuth::ParamMap paramMap; paramMap.insert("count", "200"); // Get 200 members // FIXME: could be more QNetworkRequest personListRequest = this->prepareRequest(url, QOAuth::GET, PersonListRequest, paramMap); emit currentJobChanged(tr("Getting a person list...")); nam.get(personListRequest); } /* * Add a new member to a list * */ void PumpController::addPersonToList(QString listId, QString personId) { qDebug() << "PumpController() adding person to list:" << personId << listId; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, AddMemberToListRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "person"); jsonVariantObject.insert("id", personId); QVariantMap jsonVariantTarget; jsonVariantTarget.insert("objectType", "collection"); jsonVariantTarget.insert("id", listId); QVariantMap jsonVariant; jsonVariant.insert("verb", "add"); jsonVariant.insert("object", jsonVariantObject); jsonVariant.insert("target", jsonVariantTarget); if (this->silentListsHandling) // In 'private' mode, address to the same as the object { QVariantList jsonAudienceTo; jsonAudienceTo.append(jsonVariantObject); // To: only the specific user jsonVariant.insert("to", jsonAudienceTo); } QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; emit currentJobChanged(tr("Adding person to list...")); nam.post(postRequest, data); } /* * Remove member from a list * */ void PumpController::removePersonFromList(QString listId, QString personId) { qDebug() << "PumpController() removing person from list:" << personId << listId; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, RemoveMemberFromListRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "person"); jsonVariantObject.insert("id", personId); QVariantMap jsonVariantTarget; jsonVariantTarget.insert("objectType", "collection"); jsonVariantTarget.insert("id", listId); QVariantMap jsonVariant; jsonVariant.insert("verb", "remove"); jsonVariant.insert("object", jsonVariantObject); jsonVariant.insert("target", jsonVariantTarget); if (this->silentListsHandling) // 'private' mode { QVariantList jsonAudienceTo; jsonAudienceTo.append(jsonVariantObject); jsonVariant.insert("to", jsonAudienceTo); } QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; emit currentJobChanged(tr("Removing person from list...")); nam.post(postRequest, data); } /* * Create a group where users can join and post messages for the other * members, similar to the StatusNet groups. * */ void PumpController::createGroup(QString name, QString summary, QString description) { qDebug() << "PumpController() creating group:" << name; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, CreateGroupRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "group"); if (!name.isEmpty()) { jsonVariantObject.insert("displayName", name); } jsonVariantObject.insert("summary", summary); jsonVariantObject.insert("content", description); QVariantMap jsonVariant; jsonVariant.insert("verb", "create"); jsonVariant.insert("object", jsonVariantObject); // Audience, To:Public // Groups created public ATM -- FIXME QVariantMap jsonVariantPublic; jsonVariantPublic.insert("objectType", "collection"); jsonVariantPublic.insert("id", "http://activityschema.org/collection/public"); QVariantList jsonVariantAudience; jsonVariantAudience.append(jsonVariantPublic); jsonVariant.insert("to", jsonVariantAudience); // To: Public QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; emit currentJobChanged(tr("Creating group...")); nam.post(postRequest, data); } /* * Join a group to be able to send messages to it. * Joining based on ID. * */ void PumpController::joinGroup(QString id) { qDebug() << "PumpController() joining group:" << id; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, JoinGroupRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "group"); jsonVariantObject.insert("id", id); QVariantMap jsonVariant; jsonVariant.insert("verb", "join"); jsonVariant.insert("object", jsonVariantObject); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; emit currentJobChanged(tr("Joining group...")); nam.post(postRequest, data); } void PumpController::leaveGroup(QString id) { qDebug() << "PumpController() leaving group:" << id; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, LeaveGroupRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "group"); jsonVariantObject.insert("id", id); QVariantMap jsonVariant; jsonVariant.insert("verb", "leave"); jsonVariant.insert("object", jsonVariantObject); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; emit currentJobChanged(tr("Leaving group...")); nam.post(postRequest, data); } /* * Get list of people who liked a specific post * */ void PumpController::getPostLikes(QString postLikesUrl) { qDebug() << "Getting likes for post" << postLikesUrl; emit currentJobChanged(tr("Getting likes...")); QOAuth::ParamMap paramMap; paramMap.insert("count", "100"); // TMP, up to 100 likes QNetworkRequest likesRequest = this->prepareRequest(postLikesUrl, QOAuth::GET, PostLikesRequest, paramMap); nam.get(likesRequest); } /* * Get comments for one specific post * * GET https://pumpserver.example/api/#objectType#/#id#/replies * or proxyed URL. URL is given by the post itself * */ void PumpController::getPostComments(QString postCommentsUrl) { qDebug() << "Getting comments for post" << postCommentsUrl; if (!urlIsInOurHost(postCommentsUrl)) // Post in another server and no proxyURL { emit currentJobChanged(tr("The comments for this post cannot be loaded " "due to missing data on the server.")); return; } emit currentJobChanged(tr("Getting comments...")); QOAuth::ParamMap paramMap; paramMap.insert("count", "200"); // TMP, up to 200 comments / FIXME? QNetworkRequest commentsRequest = this->prepareRequest(postCommentsUrl, QOAuth::GET, PostCommentsRequest, paramMap); nam.get(commentsRequest); } /* * Get list of people who shared a specific post * */ void PumpController::getPostShares(QString postSharesUrl) { // TODO } /* * Get a feed * * Major timelines: "Timeline", "Messages", "Activity" and "Favorites" * * GET https://pumpserver.example/api/username/inbox/major * * /inbox/direct/major = Direct timeline, posts with the user's address in * the "To:" field, that is, sent explicitly to the user * * /feed/major = Activity timeline, user's own posts * * /favorites = Favorites timeline, posts where user clicked "like" * * Minor feeds: "Meanwhile", "Mentions" and "Actions" * * GET https://pumpserver.example/api/username/inbox/minor * /inbox/direct/minor * /feed/minor * */ void PumpController::getFeed(PumpController::requestTypes feedType, int itemCount, QString url, int feedOffset) { // Name of the feed and API path QStringList feedNameAndPath = this->getFeedNameAndPath(feedType); qDebug() << "PumpController::getFeed() " << feedNameAndPath; emit currentJobChanged(tr("Getting '%1'...", "%1 is the name of a feed") .arg(feedNameAndPath.first())); QOAuth::ParamMap paramMap; paramMap.insert("count", QString("%1").arg(itemCount).toLocal8Bit()); if (feedOffset != 0) { paramMap.insert("offset", QString("%1").arg(feedOffset).toLocal8Bit()); } if (url.isEmpty()) { url = this->apiBaseUrl + feedNameAndPath.last(); } else { if (url.contains("?")) // Only if there are parameters in the URL { // FIXME: This should be made safer QStringList splitUrl = url.split("?"); url = splitUrl.first(); QStringList splitParams = splitUrl.last().split("="); paramMap.insert(splitParams.first().toUtf8(), // 'before' or 'since' splitParams.last().toUtf8()); // An activity ID } } QNetworkRequest feedRequest = this->prepareRequest(url, QOAuth::GET, feedType, paramMap); /* qDebug() << "PumpController::getFeed() ****\nfeedRequest:\n"; qDebug() << feedRequest.rawHeader("Authorization"); qDebug() << "\n*\n\nURL: " << feedRequest.url().toString() << "\n\n*******"; */ nam.get(feedRequest); } QStringList PumpController::getFeedNameAndPath(int feedType) { QStringList nameAndPath; switch (feedType) { case MainTimelineRequest: nameAndPath << tr("Timeline") << "/inbox/major"; break; case DirectTimelineRequest: nameAndPath << tr("Messages") << "/inbox/direct/major"; break; case ActivityTimelineRequest: nameAndPath << tr("Activity") << "/feed/major"; break; case FavoritesTimelineRequest: nameAndPath << tr("Favorites") << "/favorites"; break; case MinorFeedMainRequest: nameAndPath << tr("Meanwhile") << "/inbox/minor"; break; case MinorFeedDirectRequest: nameAndPath << tr("Mentions") << "/inbox/direct/minor"; break; case MinorFeedActivityRequest: nameAndPath << tr("Actions") << "/feed/minor"; break; case UserTimelineRequest: nameAndPath << tr("User timeline") << ""; //// FIXME? break; default: nameAndPath << "" << ""; qDebug() << "PumpController::getFeedNameAndPath() wrong feed type!"; } return nameAndPath; } QString PumpController::getFeedApiUrl(int feedType) { QString fullFeedUrl = this->apiBaseUrl; fullFeedUrl.append(this->getFeedNameAndPath(feedType).last()); return fullFeedUrl; } /* * Prepare a QNetworkRequest with OAuth header, content type and user agent. * */ QNetworkRequest PumpController::prepareRequest(QString url, QOAuth::HttpMethod method, requestTypes requestType, QOAuth::ParamMap paramMap, QString contentTypeString) { QByteArray authorizationHeader = qoauth->createParametersString(url, method, this->token, this->tokenSecret, QOAuth::HMAC_SHA1, paramMap, QOAuth::ParseForHeaderArguments); //qDebug() << "QOAuth::error()" << qoauth->error() << " (200=OK)"; QNetworkRequest request; // Don't append inline parameters if they're empty, that can mess up things if (!paramMap.isEmpty()) { url.append(qoauth->inlineParameters(paramMap, QOAuth::ParseForInlineQuery)); } request.setUrl(QUrl(url)); // Only add Authorization header if we're requesting something in our server if (request.url().host() == this->serverURL) { // Un-percent-encode the URL; needed to avoid "invalid signature" error // when parameters have special chars like % or / url = QByteArray::fromPercentEncoding(url.toLocal8Bit()); request.setUrl(QUrl(url)); request.setRawHeader("Authorization", authorizationHeader); } request.setHeader(QNetworkRequest::ContentTypeHeader, contentTypeString); request.setRawHeader("User-Agent", userAgentString); request.setAttribute(QNetworkRequest::User, QVariant(requestType)); return request; } /* * Generate JSON plaintext from a VarianMap. * Uses QJSON when building with Qt 4.x, and the built in Qt 5 method * when building with Qt 5.x. * */ QByteArray PumpController::prepareJSON(QVariantMap jsonVariantMap) { #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // Qt 4 version, using QJSON QJson::Serializer serializer; // bool ok; // QByteArray data = serializer.serialize(jsonVariantMap, &ok); // Without using the bool, to make it work with QJSON 0.7.x return serializer.serialize(jsonVariantMap); #else // Qt 5 version, with built-in methods QJsonDocument jsonDocument; jsonDocument = QJsonDocument::fromVariant(jsonVariantMap); return jsonDocument.toJson(); #endif } /* * Read JSON and generate a QVariantMap * Uses QJSON when building with Qt 4.x, and built-in Qt 5 methods otherwise * */ QVariantMap PumpController::parseJSON(QByteArray rawData, bool *parsedOk) { QVariantMap jsonData; bool parsed; #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // Qt 4 version, requires QJSON QJson::Parser jsonParser; jsonData = jsonParser.parse(rawData, &parsed).toMap(); #else // Qt 5 version, built-in methods QJsonDocument jsonDocument; jsonDocument = QJsonDocument::fromJson(rawData); jsonData = jsonDocument.toVariant().toMap(); parsed = !jsonDocument.isNull(); // tmp? FIXME #endif *parsedOk = parsed; return jsonData; } /* * Upload a file to the /uploads feed for the user * * Used to upload pictures, audio, video and misc files * */ QNetworkReply *PumpController::uploadFile(QString filename, QString contentType, requestTypes uploadType) { qDebug() << "PumpController::uploadFile()" << filename << contentType; QString url = this->apiBaseUrl + "/uploads"; QNetworkRequest postRequest = this->prepareRequest(url, QOAuth::POST, uploadType, QOAuth::ParamMap(), contentType); QFile file(filename); file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); // FIXME: This can use a lot of RAM with big files file.close(); this->showStatusMessageAndLogIt(tr("Uploading %1", "1=filename").arg(filename) + QString(" (%1, %2)") .arg(contentType) .arg(MiscHelpers::fileSizeString(filename))); return nam.post(postRequest, data); } QList PumpController::processAudience(QMap audienceMap) { QVariantList jsonVariantTo; QVariantList jsonVariantCC; QVariantMap jsonVariantAudienceItem; while (!audienceMap.isEmpty()) { jsonVariantAudienceItem.clear(); ///////////////////////////////////////////////////////////////// To: if (audienceMap.keys().contains("to|collection")) { QString collectionId = audienceMap.take("to|collection"); jsonVariantAudienceItem.insert("objectType", "collection"); jsonVariantAudienceItem.insert("id", collectionId); jsonVariantTo.append(jsonVariantAudienceItem); } else if (audienceMap.keys().contains("to|person")) { QString personId = audienceMap.take("to|person"); jsonVariantAudienceItem.insert("objectType", "person"); jsonVariantAudienceItem.insert("id", personId); jsonVariantTo.append(jsonVariantAudienceItem); } else if(audienceMap.keys().contains("to|group")) { QString groupId = audienceMap.take("to|group"); jsonVariantAudienceItem.insert("objectType", "group"); jsonVariantAudienceItem.insert("id", groupId); jsonVariantTo.append(jsonVariantAudienceItem); } ///////////////////////////////////////////////////////////////// Cc: else if (audienceMap.keys().contains("cc|collection")) { QString collectionId = audienceMap.take("cc|collection"); jsonVariantAudienceItem.insert("objectType", "collection"); jsonVariantAudienceItem.insert("id", collectionId); jsonVariantCC.append(jsonVariantAudienceItem); } else if (audienceMap.keys().contains("cc|person")) { QString personId = audienceMap.take("cc|person"); jsonVariantAudienceItem.insert("objectType", "person"); jsonVariantAudienceItem.insert("id", personId); jsonVariantCC.append(jsonVariantAudienceItem); } else if(audienceMap.keys().contains("cc|group")) { QString groupId = audienceMap.take("cc|group"); jsonVariantAudienceItem.insert("objectType", "group"); jsonVariantAudienceItem.insert("id", groupId); jsonVariantTo.append(jsonVariantAudienceItem); } } QList jsonVariantToAndCCList; jsonVariantToAndCCList.append(jsonVariantTo); jsonVariantToAndCCList.append(jsonVariantCC); return jsonVariantToAndCCList; } bool PumpController::urlIsInOurHost(QString url) { return (QUrl(url).host() == this->serverURL); } void PumpController::addCommentUrlToSeenList(QString id, QString url) { if (urlIsInOurHost(url)) { this->postsEverSeen.insert(id, url); } } QString PumpController::commentsUrlForPost(QString id) { return this->postsEverSeen.value(id).toString(); } void PumpController::showTransientMessage(QString message) { emit transientStatusBarMessage(message); } void PumpController::showStatusMessageAndLogIt(QString message, QString url) { emit currentJobChanged(message); emit logMessage(message, url); } /* * Show a message in the status bar with a snippet from a post or comment, * or its title, and add it to the log. * */ void PumpController::showObjectSnippetAndLogIt(QString message, QVariantMap jsonMap, QString messageWhenTitled) { QString title = jsonMap.value("object").toMap() .value("displayName").toString().trimmed(); QString snippet; if (title.isEmpty()) { QString content = jsonMap.value("object").toMap() .value("content").toString(); snippet = MiscHelpers::htmlToPlainText(content, 40); // Limit to 40 chars } else { if (!messageWhenTitled.isEmpty()) { message = messageWhenTitled; } snippet = title; } emit currentJobChanged(message.arg("\"" + snippet + "\"")); emit logMessage(message.arg(""" + snippet + """)); } void PumpController::setIgnoreSslErrors(bool state) { this->ignoreSslErrors = state; this->qoauth->setIgnoreSslErrors(state); } void PumpController::setIgnoreSslInImages(bool state) { this->ignoreSslInImages = state; } void PumpController::setNoHttpsMode() { this->serverScheme = "http://"; // Instead of the default https:// this->updateApiUrls(); // Update API base URL with this scheme } void PumpController::setSilentFollows(bool state) { this->silentFollows = state; } void PumpController::setSilentLists(bool state) { this->silentListsHandling = state; } void PumpController::setSilentLikes(bool state) { this->silentLikes = state; } void PumpController::updatePostsEverSeen(QVariantMap postMap) { this->postsEverSeen = postMap; } QVariantMap PumpController::getPostsEverSeen() { return this->postsEverSeen; } /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ /*********************************** SLOTS *********************************/ /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ void PumpController::requestFinished(QNetworkReply *reply) { int httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); int requestType = reply->request().attribute(QNetworkRequest::User).toInt(); bool finished = reply->isFinished(); QString replyUrl = reply->url().toString(); QString replyHost = reply->url().host(); QString replyErrorString = reply->errorString(); QByteArray replyFirstLineRaw = reply->readLine(); QByteArray replyData = replyFirstLineRaw + reply->readAll(); QString replyFirstLine = QString(replyFirstLineRaw); QString prettyLogMessage; qDebug() << "Request finished. HTTP code:" << httpCode; qDebug() << "Size:" << replyData.length() << "bytes; URL:" << replyUrl; qDebug() << "isFinished()?" << finished << "; Request type:" << requestType; // We got all necessary data, clean up reply->deleteLater(); // Special control after sending a post or a comment if (httpCode != 200) // if not OK { if (replyData.startsWith("{")) // Looks like JSON containing an error message { qDebug() << "Parsing JSON error message"; bool jsonErrorParsed = false; QVariantMap jsonError = this->parseJSON(replyData, &jsonErrorParsed); if (jsonErrorParsed) { // Since it's a JSON-encoded error message, use it instead of // the first data line; These can have Unicode symbols QString jsonErrorString = jsonError.value("error").toString() .trimmed(); if (!jsonErrorString.isEmpty()) { replyFirstLine = jsonErrorString; } } } if (requestType == PublishPostRequest) { emit postPublishingFailed(); } else if (requestType == UpdatePostRequest) { emit postPublishingFailed(); // kinda TMP } else if (requestType == CommentPostRequest) { emit commentPostingFailed(); } else if (requestType == UpdateCommentRequest) { emit commentPostingFailed(); // also, kinda TMP } else if (requestType == UploadMediaForPostRequest) { emit postPublishingFailed(); // FIXME? } else if (requestType == MediaRequest) { emit downloadFailed(replyUrl); } else if (requestType == ImageRequest) { this->notifyImageFailed(replyUrl); } else if (requestType == UserTimelineRequest) { emit userTimelineFailed(); } } const QString httpErrorString = tr("HTTP error", "For the following HTTP error codes" "you can check " "http://en.wikipedia.org/wiki/List_of_HTTP_status_codes " "in your language") + ": "; QString errorTypeString; switch (httpCode) { //////////////////////////////////////////////// First, handle error codes case 504: errorTypeString = httpErrorString + tr("Gateway Timeout", "HTTP 504 error string") + " (504)"; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); qDebug() << "HTTP 504: Gateway Timeout."; qDebug() << "Data: " << replyData; return; case 503: errorTypeString = httpErrorString + tr("Service Unavailable", "HTTP 503 error string") + " (503) - " + replyFirstLine; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); // if not just an image, it's important, so popup notification too if (requestType != ImageRequest && requestType != AvatarRequest) { emit showNotification(errorTypeString + "\n" + replyUrl); } qDebug() << "HTTP 503: Service Unavailable."; qDebug() << "Data: " << replyData; return; case 501: errorTypeString = httpErrorString + tr("Not Implemented", "HTTP 501 error string") + " (501) - " + replyFirstLine; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); qDebug() << "HTTP 501: Not Implemented."; qDebug() << "Data: " << replyFirstLineRaw; return; case 500: errorTypeString = httpErrorString + tr("Internal Server Error", "HTTP 500 error string") + " (500) - " + replyFirstLine; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); // if not just an image, it's important, so popup notification too if (requestType != ImageRequest && requestType != AvatarRequest) { emit showNotification(errorTypeString + "\n" + replyUrl); } qDebug() << "HTTP 500: Internal Server Error."; qDebug() << "Data: " << replyData; return; case 410: errorTypeString = httpErrorString + tr("Gone", "HTTP 410 error string") + " (410) " + replyFirstLine; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); qDebug() << "HTTP 410: Gone."; qDebug() << "Data: " << replyFirstLineRaw; return; case 404: errorTypeString = httpErrorString + tr("Not Found", "HTTP 404 error string") + " (404)"; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); qDebug() << "HTTP 404: Not Found."; qDebug() << "Data: " << replyFirstLineRaw; return; case 403: errorTypeString = httpErrorString + tr("Forbidden", "HTTP 403 error string") + " (403) - " + replyFirstLine; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); qDebug() << "HTTP 403: Forbidden."; qDebug() << "Data: " << replyFirstLineRaw; return; case 401: errorTypeString = httpErrorString + tr("Unauthorized", "HTTP 401 error string") + " (401) - " + replyFirstLine; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); qDebug() << "HTTP 401: Unauthorized."; qDebug() << "Data: " << replyFirstLineRaw; return; case 400: errorTypeString = httpErrorString + tr("Bad Request", "HTTP 400 error string") + " (400) - " + replyFirstLine; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); // if not just an image, it's important, so popup notification too if (requestType != ImageRequest && requestType != AvatarRequest) { emit showNotification(errorTypeString + "\n" + replyUrl); } qDebug() << "HTTP 400: Bad Request."; qDebug() << "Data: " << replyData; return; case 302: errorTypeString = httpErrorString + tr("Moved Temporarily", "HTTP 302 error string") + " (302)"; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); qDebug() << "HTTP 302: Moved Temporarily."; qDebug() << "Data: " << replyFirstLineRaw; return; case 301: errorTypeString = httpErrorString + tr("Moved Permanently", "HTTP 301 error string") + " (301)"; emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); qDebug() << "HTTP 301: Moved Permanently."; qDebug() << "Data: " << replyFirstLineRaw; return; case 0: // Other kinds of network errors errorTypeString = tr("Error connecting to %1").arg(replyHost); emit currentJobChanged(errorTypeString + ": " + replyErrorString); emit logMessage(errorTypeString + ": " + replyErrorString, replyUrl); qDebug() << "Error connecting to" << replyHost << ": " << replyErrorString; return; // Other HTTP codes default: errorTypeString = tr("Unhandled HTTP error code %1").arg(httpCode); emit currentJobChanged(errorTypeString + ": " + replyUrl); emit logMessage(errorTypeString, replyUrl); qDebug() << "Unhandled HTTP error " << httpCode; qDebug() << "Data: " << replyData; return; //////////////////////////////////////// The good one! case 200: qDebug() << "HTTP 200: OK!"; } // At this point, httpCode should be 200 = OK // Stuff for JSON parsing bool jsonParsedOK = false; QVariantMap jsonData; QVariantList jsonDataList; // Unless it was an AvatarRequest, ImageRequest or MediaRequest, // it should be JSON, so parse it if (requestType != AvatarRequest && requestType != ImageRequest && requestType != MediaRequest) { jsonData = this->parseJSON(replyData, &jsonParsedOK); qDebug() << "JSON data size (items):" << jsonData.size(); qDebug() << "Keys:" << jsonData.keys(); } ////////////////////////////////////////////////////////////////// switch (requestType) { case ClientRegistrationRequest: qDebug() << "Client Registration was requested"; qDebug() << "Raw JSON:" << jsonData; if (jsonParsedOK && jsonData.size() > 0) { this->clientID = jsonData["client_id"].toString(); this->clientSecret = jsonData["client_secret"].toString(); // FIXME: error control, etc. // check if jsonData.keys().contains("client_id") !! QSettings settings; settings.setValue("clientID", this->clientID); settings.setValue("clientSecret", this->clientSecret); settings.sync(); this->getToken(); } break; case UserProfileRequest: qDebug() << "A user profile was requested"; if (jsonParsedOK && jsonData.size() > 0) { QVariantMap profileMap = jsonData["profile"].toMap(); if (profileMap.value("id").toString() == "acct:" + this->userId) { qDebug() << "Received OWN profile; keys:" << profileMap.keys(); qDebug() << "Links from profile:" << profileMap.value("links").toMap().keys(); qDebug() << "Lists from profile:" << profileMap.value("lists").toMap() .value("totalItems").toInt(); qDebug() << "Lists URL:" << profileMap.value("lists").toMap() .value("url").toString(); this->haveProfile = true; QString profImageUrl = profileMap.value("image").toMap() .value("url").toString(); QString profDisplayName = profileMap.value("displayName").toString(); QString profLocation = profileMap.value("location").toMap() .value("displayName").toString(); QString profSummary = profileMap.value("summary").toString(); QString userEmail = jsonData["email"].toString(); qDebug() << "E-mail configured for the account:" << userEmail; emit profileReceived(profImageUrl, profDisplayName, profLocation, profSummary, userEmail); // Store also the user's followers URL, for posting to Followers this->userFollowersURL = profileMap.value("followers").toMap() .value("url").toString(); this->userFollowersCount = profileMap.value("followers").toMap() .value("totalItems").toInt(); this->userFollowingCount = profileMap.value("following").toMap() .value("totalItems").toInt(); qDebug() << "Followers count:" << userFollowersCount; qDebug() << "Following count:" << userFollowingCount; this->showStatusMessageAndLogIt(tr("Profile received.") + " " + tr("Followers") + QString(": %1;") .arg(QLocale::system() .toString(userFollowersCount)) + " " + tr("Following") + QString(": %1") .arg(QLocale::system() .toString(userFollowingCount))); } else { qDebug() << "Expected profile information; got:"; qDebug() << replyData; } } break; case UpdateProfileRequest: this->showStatusMessageAndLogIt(tr("Profile updated.")); this->getUserProfile(this->userId); emit userDidSomething(); break; case UpdateEmailRequest: this->showStatusMessageAndLogIt(tr("E-mail updated: %1") .arg(jsonData.value("email") .toString())); this->getUserProfile(this->userId); // TMP, FIXME... break; //////////////////////////////////// case PublishPostRequest: if (jsonParsedOK && jsonData.size() > 0) { qDebug() << "JSON parsed OK"; QString objectId = jsonData.value("object").toMap() .value("id").toString(); qDebug() << "Post ID:" << objectId; QString objectType = jsonData.value("object").toMap() .value("objectType").toString(); if (objectType == "image" || objectType == "audio" || objectType == "video" || objectType == "file") { QString translatedObjectType = ASObject::getTranslatedType(objectType); this->showStatusMessageAndLogIt(tr("%1 published successfully. " "Updating post content...", "%1 is the type of object: " "note, image...") .arg(translatedObjectType)); // Update the object with title and description // (workaround for the non-title-non-description issue) this->updatePost(objectId, objectType, this->currentPostDescription, this->currentPostTitle); } else { // Not a a media post, notify "posted OK" this->showObjectSnippetAndLogIt(tr("Untitled post %1 " "published successfully.", "%1 is a piece of the post"), jsonData, tr("Post %1 published successfully.", "%1 is the title of the post")); emit postPublished(); qDebug() << "Non-media post published correctly"; } } else { qDebug() << "Error parsing received JSON data!"; qDebug() << "Raw data:" << replyData; // JSON directly } break; case PublishAvatarRequest: this->showStatusMessageAndLogIt(tr("Avatar published successfully.")); if (jsonParsedOK && jsonData.size() > 0) { qDebug() << "JSON parsed OK"; QString imageUrl = jsonData["object"].toMap().value("image").toMap().value("url").toString(); qDebug() << "Avatar post ID:" << imageUrl; // FIXME: get "pump_io: fullImage: url" too if (jsonData["object"].toMap().value("objectType").toString() == "image") { emit avatarUploaded(imageUrl); } else { qDebug() << "Avatar uploaded, but type is not IMAGE!"; } } else { qDebug() << "Error parsing received JSON data!"; qDebug() << "Raw data:" << replyData; // JSON directly } break; //////////////////////////////////// case UpdatePostRequest: if (jsonParsedOK && jsonData.size() > 0) { this->showObjectSnippetAndLogIt(tr("Untitled post %1 " "updated successfully.", "%1 is a piece of the post"), jsonData, tr("Post %1 updated successfully.", "%1 is the title of the post")); } emit postPublished(); break; case UpdateCommentRequest: if (jsonParsedOK && jsonData.size() > 0) { this->showObjectSnippetAndLogIt(tr("Comment %1 updated successfully.", "%1 is a piece of the comment"), jsonData); } emit commentPosted(); emit userDidSomething(); break; ///////////////////////////////////// If liking a post was requested case LikePostRequest: this->showStatusMessageAndLogIt(tr("Message liked or unliked " "successfully.")); emit likeSet(); emit userDidSomething(); break; ///////////////////////////////////// If the likes for a post were requested case PostLikesRequest: qDebug() << "Likes for a post were requested" << replyUrl; if (jsonParsedOK && jsonData.size() > 0) { qDebug() << "JSON parsed OK"; emit currentJobChanged(tr("Likes received.")); jsonDataList = jsonData["items"].toList(); qDebug() << "Number of items in comments list:" << jsonDataList.size(); emit likesReceived(jsonDataList, replyUrl); } else { qDebug() << "Error parsing received comment JSON data!"; qDebug() << "Raw data:" << replyData; } break; ///////////////////////////////////// If commenting on a post was requested case CommentPostRequest: if (jsonParsedOK && jsonData.size() > 0) { this->showObjectSnippetAndLogIt(tr("Comment %1 posted successfully.", "%1 is a piece of the comment"), jsonData); } emit commentPosted(); // This will be caught by Commenter() emit userDidSomething(); break; ///////////////////////////////////// If the comments for a post were requested case PostCommentsRequest: qDebug() << "Comments for a post were requested" << replyUrl; if (jsonParsedOK && jsonData.size() > 0) { qDebug() << "JSON parsed OK"; jsonDataList = jsonData["items"].toList(); int commentCount = jsonDataList.size(); qDebug() << "Number of items in comments list:" << commentCount; if (commentCount == 1) { emit currentJobChanged(tr("1 comment received.")); } else { emit currentJobChanged(tr("%1 comments received.") .arg(commentCount)); } emit commentsReceived(jsonDataList, replyUrl); } else { qDebug() << "Error parsing received comment JSON data!"; qDebug() << "Raw data:" << replyData; } break; case SharePostRequest: qDebug() << "Post shared OK"; if (jsonParsedOK && jsonData.size() > 0) { // FIXME: this should be done using ASActivity+ASObject QVariantMap authorMap = jsonData.value("object").toMap().value("author").toMap(); QString authorName = authorMap.value("displayName").toString(); if (authorName.isEmpty()) { authorName = ASPerson::cleanupId(authorMap.value("id").toString()); } this->showStatusMessageAndLogIt(tr("Post by %1 shared successfully.", "1=author of the post we are sharing") .arg(authorName)); emit userDidSomething(); } break; case PostSharesRequest: // TODO break; /////////////////////////////////////////////// If a feed was requested case MainTimelineRequest: // just jump to the next case DirectTimelineRequest: // just jump to next case ActivityTimelineRequest: // just... yeah, jump case FavoritesTimelineRequest: // and jump! case MinorFeedMainRequest: // jump! case MinorFeedDirectRequest: // jump, jump, jump!! case MinorFeedActivityRequest: // everybody jump! case UserTimelineRequest: prettyLogMessage = tr("Received '%1'.", "%1 is the name of a feed") .arg(this->getFeedNameAndPath(requestType).first()); if (jsonParsedOK && jsonData.size() > 0) { qDebug() << "JSON parsed OK"; jsonDataList = jsonData["items"].toList(); qDebug() << "Number of items in this feed block:" << jsonDataList.size(); if (jsonDataList.size() > 0) { prettyLogMessage.append(" " + tr("Adding items...")); } // Emit this before the other signals emit currentJobChanged(prettyLogMessage); int totalItems = jsonData.value("totalItems").toInt(); QString previousLink = jsonData.value("links").toMap() .value("prev").toMap() .value("href").toString(); QString nextLink = jsonData.value("links").toMap() .value("next").toMap() .value("href").toString(); if (requestType == MainTimelineRequest) { qDebug() << "It was the main timeline"; this->haveMainTL = true; emit mainTimelineReceived(jsonDataList, previousLink, nextLink, totalItems); } else if (requestType == DirectTimelineRequest) { qDebug() << "It was the direct messages timeline"; this->haveDirectTL = true; emit directTimelineReceived(jsonDataList, previousLink, nextLink, totalItems); } else if (requestType == ActivityTimelineRequest) { qDebug() << "It was the own activity timeline"; this->haveActivityTL = true; emit activityTimelineReceived(jsonDataList, previousLink, nextLink, totalItems); } else if (requestType == FavoritesTimelineRequest) { qDebug() << "It was the favorites timeline"; this->haveFavoritesTL = true; emit favoritesTimelineReceived(jsonDataList, previousLink, nextLink, totalItems); } else if (requestType == UserTimelineRequest) { QString timelineUrl = jsonData.value("url").toString(); qDebug() << "It was a user timeline:" << timelineUrl; emit userTimelineReceived(jsonDataList, previousLink, nextLink, totalItems, timelineUrl); } else if (requestType == MinorFeedMainRequest) { qDebug() << "It was the Meanwhile feed"; this->haveMainMF = true; emit minorFeedMainReceived(jsonDataList, previousLink, nextLink, totalItems); } else if (requestType == MinorFeedDirectRequest) { qDebug() << "It was the Mentions feed"; this->haveDirectMF = true; emit minorFeedDirectReceived(jsonDataList, previousLink, nextLink, totalItems); } else if (requestType == MinorFeedActivityRequest) { qDebug() << "It was the Actions feed"; this->haveActivityMF = true; emit minorFeedActivityReceived(jsonDataList, previousLink, nextLink, totalItems); } } else { qDebug() << "Error parsing received JSON data!"; qDebug() << "Raw data:" << replyData; // JSON directly } break; case DeletePostRequest: this->showStatusMessageAndLogIt(tr("Message deleted successfully.")); emit userDidSomething(); break; case FollowContactRequest: // Just jump to next case UnfollowContactRequest: if (jsonParsedOK && jsonData.size() > 0) { ASPerson *contact = new ASPerson(jsonData.value("object").toMap(), this); if (requestType == FollowContactRequest) { prettyLogMessage = tr("Following %1 (%2) successfully.", "%1 is a person's name, %2 is the ID") .arg(contact->getName()) .arg(contact->getId()); emit contactFollowed(contact); emit followingListChanged(); } else { prettyLogMessage = tr("Stopped following %1 (%2) successfully.", "%1 is a person's name, %2 is the ID") .arg(contact->getName()) .arg(contact->getId()); emit contactUnfollowed(contact); emit followingListChanged(); } this->showStatusMessageAndLogIt(prettyLogMessage); emit userDidSomething(); } break; case FollowingListRequest: // just go to the next case FollowersListRequest: qDebug() << "A contact list was requested"; if (jsonParsedOK && jsonData.size() > 0) { qDebug() << "JSON parsed OK"; QVariant contactsVariant = jsonData.value("items"); int totalCollectionCount = jsonData.value("totalItems").toInt(); if (contactsVariant.type() == QVariant::List) { qDebug() << "Parsed a List, listing contacts..."; int receivedItemsCount = contactsVariant.toList().size(); QString batchInfoString; if (requestType == FollowingListRequest) { this->userFollowingCount = totalCollectionCount; totalReceivedFollowing += receivedItemsCount; emit contactListReceived("following", contactsVariant.toList(), this->totalReceivedFollowing); batchInfoString = QString(" (%1/%2)") .arg(totalReceivedFollowing) .arg(userFollowingCount); if (totalReceivedFollowing >= totalCollectionCount) { this->haveFollowing = true; this->showStatusMessageAndLogIt(tr("List of 'following' " "completely received.") + batchInfoString); emit followingListChanged(); } else { emit currentJobChanged(tr("Partial list of 'following' " "received.") + batchInfoString); qDebug() << "Partial following received:" << batchInfoString; } } else // == FollowersListRequest { this->userFollowersCount = totalCollectionCount; totalReceivedFollowers += receivedItemsCount; emit contactListReceived("followers", contactsVariant.toList(), this->totalReceivedFollowers); batchInfoString = QString(" (%1/%2)") .arg(totalReceivedFollowers) .arg(userFollowersCount); if (totalReceivedFollowers >= totalCollectionCount) { this->haveFollowers = true; this->showStatusMessageAndLogIt(tr("List of 'followers' " "completely received.") + batchInfoString); } else { emit currentJobChanged(tr("Partial list of 'followers' " "received.") + batchInfoString); qDebug() << "Partial following received:" << batchInfoString; } } } else { qDebug() << "Expected a list of contacts, received something else:"; qDebug() << jsonData; } } else { qDebug() << "Error parsing received JSON data!"; qDebug() << "Raw data:" << replyData; // JSON directly } break; case ListsListRequest: qDebug() << "The list of person lists was requested"; if (jsonParsedOK && jsonData.size() > 0) { qDebug() << "JSON parsed OK"; QVariant listsVariant = jsonData.value("items"); if (listsVariant.type() == QVariant::List) { qDebug() << "Parsed a List, listing lists..."; this->havePersonLists = true; emit currentJobChanged(tr("List of 'lists' received.")); emit listsListReceived(listsVariant.toList()); } else { qDebug() << "Expected a list of lists, received something else:"; qDebug() << jsonData; } } else { qDebug() << "Error parsing received JSON data!"; qDebug() << "Raw data:" << replyData; // JSON directly } break; case SiteUserListRequest: this->showStatusMessageAndLogIt(tr("List of %1 users received.", "%1 is a server name") .arg(this->serverURL)); if (jsonParsedOK && jsonData.size() > 0) { qDebug() << "JSON parsed OK"; emit siteUserListReceived(jsonData.value("items").toList(), jsonData.value("totalItems").toInt()); //qDebug() << "\n\n*** Site User List:\n\n" << replyData; } break; ///////////////////////////////////////////////////////////////////// Lists // Person list created OK case CreatePersonListRequest: this->showStatusMessageAndLogIt(tr("Person list '%1' created successfully.") .arg(jsonData.value("object").toMap() .value("displayName").toString())); this->getListsList(); // And reload the person lists (FIXME!) emit userDidSomething(); break; // Person list deleted OK case DeletePersonListRequest: this->showStatusMessageAndLogIt(tr("Person list deleted successfully.")); this->getListsList(); // And reload the person lists (FIXME!) emit userDidSomething(); break; case PersonListRequest: qDebug() << "A person list was requested"; if (jsonParsedOK && jsonData.size() > 0) { qDebug() << "JSON parsed OK"; QVariant personListVariant = jsonData.value("items"); if (personListVariant.type() == QVariant::List) { qDebug() << "Parsed a List, listing people in list..." << jsonData.value("displayName").toString(); emit currentJobChanged(tr("Person list received.")); // Using list URL as ID, since the ID isn't provided here... emit personListReceived(personListVariant.toList(), jsonData.value("url").toString()); } else { qDebug() << "Expected a list of people, received something else:"; qDebug() << jsonData; } } else { qDebug() << "Error parsing received JSON data!"; qDebug() << "Raw data:" << replyData; // JSON directly } break; // Person added to list OK case AddMemberToListRequest: if (jsonParsedOK && jsonData.size() > 0) { QString personId = ASPerson::cleanupId(jsonData.value("object").toMap() .value("id").toString()); QString personName = jsonData.value("object").toMap() .value("displayName").toString(); QString personAvatar = jsonData.value("object").toMap() .value("image").toMap() .value("url").toString(); emit personAddedToList(personId, personName, personAvatar); this->showStatusMessageAndLogIt(tr("%1 (%2) added to list successfully.", "1=contact name, 2=contact ID") .arg(personName) .arg(personId)); emit userDidSomething(); } break; // Person removed from list OK case RemoveMemberFromListRequest: if (jsonParsedOK && jsonData.size() > 0) { QString personId = ASPerson::cleanupId(jsonData.value("object").toMap() .value("id").toString()); QString personName = jsonData.value("object").toMap() .value("displayName").toString(); emit personRemovedFromList(personId); this->showStatusMessageAndLogIt(tr("%1 (%2) removed from list successfully.", "1=contact name, 2=contact ID") .arg(personName) .arg(personId)); emit userDidSomething(); } break; //////////////////////////////////////////////////////////////////// Groups case CreateGroupRequest: this->showStatusMessageAndLogIt(tr("Group %1 created successfully.") .arg(jsonData.value("object").toMap() .value("displayName").toString())); emit userDidSomething(); qDebug() << "Group created; ID:" << jsonData.value("object").toMap() .value("id").toString(); break; case JoinGroupRequest: this->showStatusMessageAndLogIt(tr("Group %1 joined successfully.") .arg(jsonData.value("object").toMap() .value("displayName").toString())); emit userDidSomething(); qDebug() << "Group joined; =========================="; qDebug() << "Keys: " << jsonData.keys(); qDebug() << jsonData.value("object").toString(); break; case LeaveGroupRequest: this->showStatusMessageAndLogIt(tr("Left the %1 group successfully.") .arg(jsonData.value("object").toMap() .value("displayName").toString())); emit userDidSomething(); break; case AvatarRequest: qDebug() << "Received AVATAR data, from " << replyUrl; if (finished) { qDebug() << "Avatar received 100%"; emit avatarPictureReceived(replyData, replyUrl); } else { qDebug() << "Avatar not complete yet"; } break; case ImageRequest: qDebug() << "Received IMAGE data, from " << replyUrl; if (finished) { qDebug() << "Image received 100%"; emit imageReceived(replyData, replyUrl); } else { qDebug() << "Image not complete yet"; } break; case MediaRequest: qDebug() << "Received MEDIA data, from " << replyUrl; if (finished) { this->showStatusMessageAndLogIt(tr("File downloaded successfully.")); emit downloadCompleted(replyUrl); qDebug() << "Media received 100%" << replyData.length() << "bytes"; } else { qDebug() << "Media not complete yet"; } break; //////////////////////////////////////// If uploading a file was requested case UploadAvatarRequest: // just jump to next case UploadMediaForPostRequest: // just jump case UploadFileRequest: qDebug() << "Uploading a file was requested"; if (jsonParsedOK && jsonData.size() > 0) { qDebug() << "JSON parsed OK"; QString uploadedFileId = jsonData["id"].toString(); qDebug() << "Uploaded file ID:" << uploadedFileId; QString objectType = jsonData["objectType"].toString(); if (objectType == "image" || objectType == "audio" || objectType == "video" || objectType == "file") { if (requestType == UploadMediaForPostRequest) { prettyLogMessage = tr("File uploaded successfully. " "Posting message..."); emit currentJobChanged(prettyLogMessage); emit logMessage(prettyLogMessage, replyUrl); this->postMediaStepTwo(uploadedFileId); } else if (requestType == UploadAvatarRequest) { this->showStatusMessageAndLogIt(tr("Avatar uploaded.")); this->postAvatarStepTwo(uploadedFileId); } } } else { qDebug() << "Error parsing received JSON data!"; qDebug() << "Raw data:" << replyData; // JSON directly } break; } // end switch (requestType) qDebug() << "requestFinished() ended; " << replyUrl; } /* * Handle SSL errors * * Default is blocking connections with SSL problems, unless the request * if for an image on a remote host and the corresponding option was set * in the Post settings, or --ignoresslerrors was used * */ void PumpController::sslErrorsHandler(QNetworkReply *reply, QList errorList) { qDebug() << "\n==== SSL errors!! ===="; qDebug() << "At:" << reply->url().toString(); qDebug() << "Error list:" << errorList << "\n\n"; QString errorsString; foreach (QSslError sslError, errorList) { errorsString.append(sslError.errorString() + "; "); } errorsString.remove(-2, 2); // remove "; " at the end this->showStatusMessageAndLogIt(tr("SSL errors in connection to %1!") .arg(reply->url().host()) + QString(" (%1)").arg(errorsString)); bool allowLoadingImage = false; if (this->ignoreSslInImages) { int requestType = reply->request().attribute(QNetworkRequest::User).toInt(); if (requestType == ImageRequest && reply->url().host() != this->serverURL) { allowLoadingImage = true; this->showStatusMessageAndLogIt(tr("Loading external image from " "%1 regardless of SSL errors, " "as configured...", "%1 is a hostname") .arg(reply->url().host()), reply->url().toString()); } } // Ignore SSL errors and continue, if configured to do so if (this->ignoreSslErrors || allowLoadingImage) { qDebug() << "Ignoring these errors..."; reply->ignoreSslErrors(); } } void PumpController::getToken() { // If we do not have client_id or client_secret, do dynamic client registration if (this->clientID.isEmpty() || this->clientSecret.isEmpty()) { qDebug() << "PumpController::getToken()"; qDebug() << "We do not have client_id/client_secret yet; " "doing Dynamic Client Registration"; this->showStatusMessageAndLogIt(tr("The application is not registered with " "your server yet. Registering...")); // POST to https://yourserver.example/api/client/register QNetworkRequest postRequest(QUrl(this->serverScheme + this->serverURL + "/api/client/register")); postRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); postRequest.setRawHeader("User-Agent", userAgentString); postRequest.setAttribute(QNetworkRequest::User, QVariant(ClientRegistrationRequest)); QByteArray data("{" " \"type\": \"client_associate\", " " \"application_type\": \"native\", " " \"application_name\": \"Dianara\", " " \"logo_uri\": \"http://dianara.nongnu.org/dianara-logo.png\", " " \"client_uri\": \"https://jancoding.wordpress.com/dianara\" " "}"); qDebug() << "About to POST:" << data; // upon receiving data (id+secret), will execute getToken() again nam.post(postRequest, data); } else { qDebug() << "Using saved client_id and client_secret:" << this->clientID << this->clientSecret; // OAuth stuff..... // 1. obtaining an unauthorized Request Token from the Service Provider, // 2. asking the User to authorize the Request Token, // 3. exchanging the Request Token for the Access Token this->showStatusMessageAndLogIt(tr("Getting OAuth token...")); qDebug() << "Doing OAuth token stuff..."; qDebug() << "NOTE: if you see a crash here, you need QCA and its " "openSSL plugin:"; qDebug() << ">>> qca2-plugin-openssl, libqca2-plugin-ossl, or similar"; qDebug() << "If you compiled Dianara from source, check the INSTALL " "file carefully"; QStringList QCAsupportedFeatures = QCA::supportedFeatures(); qDebug() << "QCA Supported Features:" << QCAsupportedFeatures; if (QCAsupportedFeatures.contains("hmac(sha1)")) { qDebug() << "HMAC-SHA1 support is OK"; } else { qDebug() << "Warning, HMAC-SHA1 doesn't seem to be supported!"; // Notify the user about missing plugin emit authorizationFailed(tr("OAuth support error"), tr("Your installation of QOAuth, a library " "used by Dianara, doesn't seem to have " "HMAC-SHA1 support.") + "\n" + tr("You probably need to install the OpenSSL " "plugin for QCA: %1, %2 or similar.") .arg("qca2-plugin-openssl") .arg("libqca2-plugin-ossl")); } qoauth->setConsumerKey(this->clientID.toLocal8Bit()); qoauth->setConsumerSecret(this->clientSecret.toLocal8Bit()); QString requestTokenUrl = this->serverScheme + this->serverURL + "/oauth/request_token"; qDebug() << "GET: " << requestTokenUrl << "with" << qoauth->consumerKey() << qoauth->consumerSecret(); QOAuth::ParamMap oAuthParams; oAuthParams.insert("oauth_callback", "oob"); QOAuth::ParamMap reply = qoauth->requestToken(requestTokenUrl, QOAuth::GET, QOAuth::HMAC_SHA1, oAuthParams); if (qoauth->error() == QOAuth::NoError) { qDebug() << "requestToken OK:" << reply.keys(); token = reply.value(QOAuth::tokenParameterName()); tokenSecret = reply.value(QOAuth::tokenSecretParameterName()); qDebug() << "Token:" << token; qDebug() << "Token Secret:" << tokenSecret.left(5) << "********** (hidden)"; QUrl oAuthAuthorizeUrl(this->serverScheme + this->serverURL + "/oauth/authorize"); #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) oAuthAuthorizeUrl.addQueryItem("oauth_token", token); #else QUrlQuery query; query.addQueryItem("oauth_token", token); oAuthAuthorizeUrl.setQuery(query); #endif QDesktopServices::openUrl(oAuthAuthorizeUrl); // Send also a signal, so AccountDialog can show the URL in // a label, in case the browser didn't launch emit openingAuthorizeURL(oAuthAuthorizeUrl); // Now, user should enter VERIFIER in AccountDialog to authorize the program } else { qDebug() << "QOAuth error" << qoauth->error() << "!"; qDebug() << reply.keys(); emit authorizationFailed(tr("Authorization error"), tr("There was an OAuth error while trying " "to get the authorization token.") + "\n\n" + tr("QOAuth error %1").arg(qoauth->error())); } } } void PumpController::authorizeApplication(QString verifierCode) { qDebug() << "Verifier code entered by user:" << verifierCode; QOAuth::ParamMap moreParams; moreParams.insert("oauth_verifier", verifierCode.toUtf8()); // verifier as QByteArray QString requestAuthorizationUrl = this->serverScheme + this->serverURL + "/oauth/access_token"; QOAuth::ParamMap reply = qoauth->accessToken(requestAuthorizationUrl, QOAuth::GET, token, tokenSecret, QOAuth::HMAC_SHA1, moreParams); if (qoauth->error() == QOAuth::NoError) // Woooohooo!! { qDebug() << "Got authorized token; Dianara is authorized to access the account"; token = reply.value(QOAuth::tokenParameterName()); tokenSecret = reply.value(QOAuth::tokenSecretParameterName()); this->isApplicationAuthorized = true; this->showStatusMessageAndLogIt(tr("Application authorized successfully.")); QSettings settings; settings.setValue("isApplicationAuthorized", this->isApplicationAuthorized); settings.setValue("token", this->token); settings.setValue("tokenSecret", this->tokenSecret); settings.sync(); qDebug() << "Token:" << token; qDebug() << "TokenSecret:" << tokenSecret; emit this->authorizationStatusChanged(isApplicationAuthorized); } else { this->showStatusMessageAndLogIt(tr("OAuth error while authorizing application.") + QString(" (%1)").arg(qoauth->error())); qDebug() << "OAuth error while authorizing application" << qoauth->error(); } } /* * Called by a QTimer; * Get initial data (profile, contacts, timelines), one step at a time * */ void PumpController::getInitialData() { qDebug() << "PumpController::getInitialData() step" << initialDataStep; initialDataTimer->setInterval(3000); // Every 3 sec // If we're still waiting for a proxy password, don't do anything if (this->needsProxyPassword()) { emit currentJobChanged(tr("Waiting for proxy password...")); return; } /* * FIXME: this needs to be way more elaborate. * * Ensure certain stuff has been received before continuing. * * For instance, getting the "following" list is needed before * being able to post to specific contacts, and to know the state * of following/not Following the author of a post in a timeline * */ switch (this->initialDataStep) { case 0: if (!haveProfile) { this->getUserProfile(this->userId); } break; case 1: // Ensure we have the profile already, before getting contact lists... if (!haveProfile) { initialDataStep = -1; // Restart initialization! this->showStatusMessageAndLogIt(tr("Still waiting for profile. " "Trying again...")); } break; case 2: if (!haveFollowing) { if (haveProfile) // Require profile first, since it provides followingCount { this->getContactList("following"); } else { --initialDataStep; // go back 1 to retry } } break; case 3: if (!haveFollowers) { if (haveProfile) // Required for followersCount { this->getContactList("followers"); } else { --initialDataStep; // retry } } break; case 4: if (!haveMainTL) { this->getFeed(PumpController::MainTimelineRequest, this->postsPerPageMain); } break; case 5: if (!haveMainMF) { this->getFeed(PumpController::MinorFeedMainRequest, 50); } break; case 6: if (!havePersonLists) { this->getListsList(); } break; case 7: if (!haveDirectTL) { this->getFeed(PumpController::DirectTimelineRequest, this->postsPerPageOther); } break; case 8: if (!haveDirectMF) { this->getFeed(PumpController::MinorFeedDirectRequest, 20); } break; case 9: if (!haveActivityTL) { this->getFeed(PumpController::ActivityTimelineRequest, this->postsPerPageOther); } break; case 10: if (!haveActivityMF) { this->getFeed(PumpController::MinorFeedActivityRequest, 20); } break; case 11: if (!haveFavoritesTL) { this->getFeed(PumpController::FavoritesTimelineRequest, this->postsPerPageOther); } break; case 12: // If some data is still missing, go back to the beginning of the cycle if (!haveProfile || !haveFollowing || !haveFollowers || !havePersonLists || !haveMainTL || !haveMainMF || !haveDirectTL || !haveDirectMF || !haveActivityTL || !haveActivityMF || !haveFavoritesTL) { // Unless we've already tried several times, like people with broken // contact lists, for instance, which will never be received if (initialDataAttempts < 5) { initialDataStep = -1; ++initialDataAttempts; QString attempts; if (initialDataAttempts > 1) { attempts = tr("%1 attempts").arg(initialDataAttempts); } else { attempts = tr("1 attempt"); } this->showStatusMessageAndLogIt(tr("Some initial data was not " "received. Restarting " "initialization...") + " (" + attempts + ")"); } else { this->showStatusMessageAndLogIt(tr("Some initial data was not " "received after several " "attempts. Something might " "be wrong with your server. " "You might still be able to " "use the service normally.")); } } else { this->showStatusMessageAndLogIt(tr("All initial data received. " "Initialization complete.")); // If there are no problems, this will just wait one interval, // so it takes longer for the final (default) step to arrive } break; default: initialDataTimer->stop(); this->showStatusMessageAndLogIt(tr("Ready.")); emit initializationCompleted(); qDebug() << "--------------------------------------"; qDebug() << "-- All initial data loaded -----------"; qDebug() << "--------------------------------------"; } emit initializationStepChanged(initialDataStep); ++initialDataStep; } /* * Send a NOTE to the server * */ void PumpController::postNote(QMap audienceMap, QString postText, QString postTitle) { qDebug() << "PumpController::postNote()"; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, PublishPostRequest); qDebug() << "Should be posting to:" << audienceMap; QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "note"); if (!postTitle.isEmpty()) { jsonVariantObject.insert("displayName", postTitle); } //jsonVariantObject.insert("summary", "summary test"); jsonVariantObject.insert("content", postText); QVariantMap jsonVariant; jsonVariant.insert("verb", "post"); jsonVariant.insert("object", jsonVariantObject); QList audience = processAudience(audienceMap); jsonVariant.insert("to", audience.at(0)); jsonVariant.insert("cc", audience.at(1)); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; nam.post(postRequest, data); } /* * Post media-type of object (image, audio, video, file) * * First, upload the file. * Then we get its ID in a signal, and create the post itself * */ QNetworkReply *PumpController::postMedia(QMap audienceMap, QString postText, QString postTitle, QString mediaFilename, QString mediaType, QString mimeContentType) { qDebug() << "PumpController::postMedia()" << mediaType; qDebug() << "Uploading" << mediaFilename << "with title:" << postTitle; // Store postTitle, postText, and audienceMap, then upload this->currentPostTitle = postTitle; this->currentPostDescription = postText; this->currentAudienceMap = audienceMap; this->currentPostType = mediaType; QNetworkReply *networkReply; networkReply = this->uploadFile(mediaFilename, mimeContentType, UploadMediaForPostRequest); return networkReply; } /* * Post Media, step 2: after getting the ID in the file upload request, * create the post itself * */ void PumpController::postMediaStepTwo(QString id) { qDebug() << "PumpController::postMediaStepTwo()" << currentPostType << "ID:" << id; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, PublishPostRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", this->currentPostType); jsonVariantObject.insert("id", id); QList audience = processAudience(this->currentAudienceMap); QVariantMap jsonVariant; jsonVariant.insert("verb", "post"); jsonVariant.insert("object", jsonVariantObject); jsonVariant.insert("to", audience.at(0)); jsonVariant.insert("cc", audience.at(1)); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; nam.post(postRequest, data); } /* * Second step for avatar upload. * * Post the image to Public * */ void PumpController::postAvatarStepTwo(QString id) { qDebug() << "PumpController::postAvatarStepTwo() image ID:" << id; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, PublishAvatarRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "image"); jsonVariantObject.insert("id", id); // audience, Cc: Public QVariantMap jsonVariantPublic; jsonVariantPublic.insert("objectType", "collection"); jsonVariantPublic.insert("id", "http://activityschema.org/collection/public"); QVariantList jsonVariantAudience; jsonVariantAudience.append(jsonVariantPublic); QVariantMap jsonVariant; jsonVariant.insert("verb", "post"); jsonVariant.insert("object", jsonVariantObject); jsonVariant.insert("cc", jsonVariantAudience); // Cc: Public QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; nam.post(postRequest, data); } /* * Update post contents (the object) * */ void PumpController::updatePost(QString id, QString type, QString content, QString title) { qDebug() << "PumpController::updatePost(), post ID:" << id; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, UpdatePostRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("id", id); jsonVariantObject.insert("objectType", type); if (!title.isEmpty()) // FIXME: needs a way to remove titles... { jsonVariantObject.insert("displayName", title); } jsonVariantObject.insert("content", content); QVariantMap jsonVariant; jsonVariant.insert("verb", "update"); jsonVariant.insert("object", jsonVariantObject); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; nam.post(postRequest, data); } /* * Like (favorite) a post, by its ID (URL) * */ void PumpController::likePost(QString postId, QString postType, QString authorId, bool like) { qDebug() << "PumpController::likePost() liking post" << postId; QNetworkRequest likeRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, LikePostRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", postType); jsonVariantObject.insert("id", postId); QVariantMap jsonVariant; jsonVariant.insert("verb", like ? "favorite":"unfavorite"); // like or unlike jsonVariant.insert("object", jsonVariantObject); if (this->silentLikes) { QVariantMap authorVariant; authorVariant.insert("objectType", "person"); authorVariant.insert("id", "acct:" + authorId); QVariantList jsonAudienceTo; jsonAudienceTo.append(authorVariant); // To: only the author jsonVariant.insert("to", jsonAudienceTo); } QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "about to POST:" << data; nam.post(likeRequest, data); } void PumpController::addComment(QString comment, QString postID, QString postType) { qDebug() << "PumpController::addComment() sending comment to this post:" << postID; QNetworkRequest commentRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, CommentPostRequest); QVariantMap jsonVariantInReplyTo; jsonVariantInReplyTo.insert("id", postID); jsonVariantInReplyTo.insert("objectType", postType); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "comment"); jsonVariantObject.insert("content", comment); jsonVariantObject.insert("inReplyTo", jsonVariantInReplyTo); QVariantMap jsonVariant; jsonVariant.insert("verb", "post"); jsonVariant.insert("object", jsonVariantObject); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "about to POST:" << data; nam.post(commentRequest, data); } /* * Update comment contents (object) * * FIXME: This should be merged with :updatePost() * */ void PumpController::updateComment(QString id, QString content) { qDebug() << "PumpController::updateComment(), comment ID:" << id; QNetworkRequest postRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, UpdateCommentRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("id", id); jsonVariantObject.insert("objectType", "comment"); jsonVariantObject.insert("content", content); QVariantMap jsonVariant; jsonVariant.insert("verb", "update"); jsonVariant.insert("object", jsonVariantObject); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "About to POST:" << data; nam.post(postRequest, data); } void PumpController::sharePost(QString postID, QString postType) { qDebug() << "PumpController::sharePost() sharing post" << postID; QNetworkRequest shareRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, SharePostRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", postType); jsonVariantObject.insert("id", postID); QVariantMap jsonVariant; jsonVariant.insert("verb", "share"); jsonVariant.insert("object", jsonVariantObject); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "about to POST:" << data; nam.post(shareRequest, data); } void PumpController::unsharePost(QString postId, QString postType) { qDebug() << "PumpController::unsharePost() unsharing post" << postId; QNetworkRequest unshareRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, UnsharePostRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", postType); jsonVariantObject.insert("id", postId); QVariantMap jsonVariant; jsonVariant.insert("verb", "unshare"); jsonVariant.insert("object", jsonVariantObject); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "about to POST:" << data; nam.post(unshareRequest, data); } void PumpController::deletePost(QString postID, QString postType) { qDebug() << "PumpController::deletePost() deleting post" << postID; QNetworkRequest deleteRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, DeletePostRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", postType); jsonVariantObject.insert("id", postID); QVariantMap jsonVariant; jsonVariant.insert("verb", "delete"); jsonVariant.insert("object", jsonVariantObject); QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "about to POST:" << data; nam.post(deleteRequest, data); } /* * Add a contact to the /following list with their webfinger address * * Info for newly added contact will come in the reply * */ void PumpController::followContact(QString address) { qDebug() << "PumpController::followContact()" << address; QNetworkRequest followRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, FollowContactRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "person"); jsonVariantObject.insert("id", "acct:" + address); QVariantMap jsonVariant; jsonVariant.insert("verb", "follow"); jsonVariant.insert("object", jsonVariantObject); if (this->silentFollows) // In 'private' mode, address to the same as the object { QVariantList jsonAudienceTo; jsonAudienceTo.append(jsonVariantObject); // To: only the specific user jsonVariant.insert("to", jsonAudienceTo); } QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "about to POST:" << data; nam.post(followRequest, data); } /* * Remove a contact from the /following list with their webfinger address * */ void PumpController::unfollowContact(QString address) { qDebug() << "PumpController::unfollowContact()" << address; QNetworkRequest unfollowRequest = this->prepareRequest(this->apiFeedUrl, QOAuth::POST, UnfollowContactRequest); QVariantMap jsonVariantObject; jsonVariantObject.insert("objectType", "person"); jsonVariantObject.insert("id", "acct:" + address); QVariantMap jsonVariant; jsonVariant.insert("verb", "stop-following"); jsonVariant.insert("object", jsonVariantObject); if (this->silentFollows) { QVariantList jsonAudienceTo; jsonAudienceTo.append(jsonVariantObject); jsonVariant.insert("to", jsonAudienceTo); } QByteArray data = this->prepareJSON(jsonVariant); qDebug() << "about to POST:" << data; nam.post(unfollowRequest, data); } dianara-v1.3.2/src/commenterblock.cpp000644 000764 000764 00000050552 12614431303 017177 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "commenterblock.h" CommenterBlock::CommenterBlock(PumpController *pumpController, GlobalObject *globalObject, QString parentAuthorId, bool parentStandalone, QWidget *parent) : QWidget(parent) { this->pController = pumpController; this->globalObj = globalObject; this->parentPostAuthorId = parentAuthorId; this->parentPostStandalone = parentStandalone; this->editingMode = false; this->currentCommentCount = 0; this->allCommentsShown = false; this->reloadCommentsString = QString::fromUtf8("\342\206\273") // Reload symbol + "  " + tr("Reload comments"); this->showAllCommentsLinkLabel = new QLabel("" + reloadCommentsString + "", this); showAllCommentsLinkLabel->setContextMenuPolicy(Qt::NoContextMenu); QFont showAllFont; showAllFont.setPointSize(showAllFont.pointSize() - 3); showAllCommentsLinkLabel->setFont(showAllFont); connect(showAllCommentsLinkLabel, SIGNAL(linkActivated(QString)), this, SLOT(requestAllComments())); this->setContentsMargins(0, 0, 0, 0); commentsLayout = new QVBoxLayout(); commentsLayout->setContentsMargins(0, 0, 0, 0); commentsLayout->setSpacing(1); commentsWidget = new QWidget(this); commentsWidget->setContentsMargins(0, 0, 0, 0); QSizePolicy sizePolicy; sizePolicy.setHeightForWidth(false); sizePolicy.setWidthForHeight(false); sizePolicy.setHorizontalPolicy(QSizePolicy::Minimum); sizePolicy.setVerticalPolicy(QSizePolicy::Maximum); commentsWidget->setSizePolicy(sizePolicy); commentsWidget->setLayout(commentsLayout); this->commentsScrollArea = new QScrollArea(this); //commentsScrollArea->setFrameStyle(QFrame::NoFrame); commentsScrollArea->setContentsMargins(0, 0, 0, 0); //commentsScrollArea->setSizePolicy(sizePolicy); commentsScrollArea->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); commentsScrollArea->setWidget(commentsWidget); commentsScrollArea->setWidgetResizable(true); this->getAllCommentsTimer = new QTimer(this); getAllCommentsTimer->setSingleShot(true); connect(getAllCommentsTimer, SIGNAL(timeout()), this, SLOT(requestAllComments())); // Hide these until setComments() is called, if there are any comments showAllCommentsLinkLabel->hide(); commentsScrollArea->hide(); scrollToBottomTimer = new QTimer(this); scrollToBottomTimer->setSingleShot(true); connect(scrollToBottomTimer, SIGNAL(timeout()), this, SLOT(scrollCommentsToBottom())); this->commentComposer = new Composer(this->globalObj, false, // forPublisher = false this); this->commentComposer->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); this->commentComposer->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); this->commentComposer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); connect(commentComposer, SIGNAL(editingFinished()), this, SLOT(sendComment())); connect(commentComposer, SIGNAL(editingCancelled()), this, SLOT(setMinimumMode())); // Formatting/Tools button exported from Composer this->toolsButton = commentComposer->getToolsButton(); // Info label about sending status this->statusInfoLabel = new QLabel(this); statusInfoLabel->setAlignment(Qt::AlignCenter); statusInfoLabel->setWordWrap(true); showAllFont.setPointSize(showAllFont.pointSize() + 1); statusInfoLabel->setFont(showAllFont); this->commentButton = new QPushButton(QIcon::fromTheme("mail-send", QIcon(":/images/button-post.png")), tr("Comment", "Infinitive verb"), this); commentButton->setToolTip("" + tr("You can press Control+Enter to send " "the comment with the keyboard")); connect(commentButton, SIGNAL(clicked()), this, SLOT(sendComment())); this->cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("Cancel"), this); cancelButton->setToolTip("" + tr("Press ESC to cancel the comment " "if there is no text")); connect(cancelButton, SIGNAL(clicked()), commentComposer, SLOT(cancelPost())); bottomLayout = new QGridLayout(); bottomLayout->setContentsMargins(0, 0, 0, 0); bottomLayout->setSpacing(1); bottomLayout->addWidget(commentComposer, 0, 0, 8, 3); bottomLayout->addWidget(toolsButton, 0, 3, 1, 1); bottomLayout->addWidget(statusInfoLabel, 1, 3, 5, 1, Qt::AlignCenter); bottomLayout->addWidget(commentButton, 6, 3, 1, 1); bottomLayout->addWidget(cancelButton, 7, 3, 1, 1); mainLayout = new QVBoxLayout(); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(1); mainLayout->addWidget(showAllCommentsLinkLabel, 0, Qt::AlignRight | Qt::AlignTop); mainLayout->addWidget(commentsScrollArea, 0); mainLayout->addLayout(bottomLayout, 0); this->setLayout(mainLayout); this->setMinimumMode(); qDebug() << "Commenter created"; } CommenterBlock::~CommenterBlock() { qDebug() << "Commenter destroyed"; } void CommenterBlock::clearComments() { foreach (Comment *comment, commentsInBlock) { comment->deleteLater(); } commentsInBlock.clear(); this->currentCommentCount = 0; } void CommenterBlock::setComments(QVariantList commentsList, int commentCount) { if (commentCount > 0) { // If some comments are actually included, clear first if (commentsList.length() > 0) { this->clearComments(); } this->currentCommentCount = commentCount; if (currentCommentCount > commentsList.size()) { allCommentsShown = false; } else { allCommentsShown = true; } this->updateShowAllLink(); foreach (QVariant commentVariant, commentsList) { ASObject *commentObject = new ASObject(commentVariant.toMap(), this); this->appendComment(commentObject); } this->commentsScrollArea->show(); // Move scrollbar to the bottom this->scrollCommentsToBottom(); // First, try // Then, some msecs later, try again, in case there was no scrollbar before, // and the first try didn't work // Trying immediately first avoids a flicker-like effect sometimes scrollToBottomTimer->start(500); } } /* * Add a single comment to the layout * */ void CommenterBlock::appendComment(ASObject *object, bool justOne) { foreach (Comment *previousComment, this->commentsInBlock) { if (previousComment->getObjectId() == object->getId()) { return; // Comment is already present, so abort } } Comment *comment = new Comment(this->pController, this->globalObj, object, this); connect(comment, SIGNAL(commentQuoteRequested(QString)), this, SLOT(quoteComment(QString))); connect(comment, SIGNAL(commentEditRequested(QString,QString)), this, SLOT(editComment(QString,QString))); // Highlight if the comment was made by the author of the parent post if (this->globalObj->getPostHLAuthorComments()) // if configured to do so { if (object->author()->getId() == this->parentPostAuthorId) { comment->setHint(this->globalObj->getColor(4)); // HL filtering color } } // Or highlight if this is our own comment, also optional if (this->globalObj->getPostHLOwnComments()) { if (object->author()->getId() == this->pController->currentUserId()) { comment->setHint(this->globalObj->getColor(3)); // Your own posts color } } // Add the comment at the end if it's just one (via MinorFeed), or at // the top when it's part of a full list, since those lists come in reverse this->commentsInBlock.append(comment); // First, keep track of it if (justOne) { this->commentsLayout->addWidget(comment); ++this->currentCommentCount; // Check if we need to show "reload comments" or "show all X comments" if (this->currentCommentCount == commentsInBlock.size()) { this->allCommentsShown = true; } this->updateShowAllLink(); // Showing this is needed if there were 0 comments before this->commentsScrollArea->show(); scrollToBottomTimer->start(500); } else // called from setComments() { this->commentsLayout->insertWidget(0, comment); } } void CommenterBlock::updateCommentFromObject(ASObject *object) { foreach (Comment *comment, this->commentsInBlock) { if (comment->getObjectId() == object->getId()) { comment->updateDataFromObject(object); } } // FIXME: this needs some cheking... commentsWidget->setMinimumHeight(10); scrollToBottomTimer->start(500); this->adjustCommentsHeight(); this->adjustCommentArea(); } void CommenterBlock::setCommentDeletedFromObject(ASObject *object) { foreach (Comment *comment, this->commentsInBlock) { if (comment->getObjectId() == object->getId()) { comment->setCommentDeleted(object->getDeletedOnString()); } } // FIXME: this needs some cheking... and is copied from previous function commentsWidget->setMinimumHeight(10); scrollToBottomTimer->start(500); this->adjustCommentsHeight(); this->adjustCommentArea(); } void CommenterBlock::updateFuzzyTimestamps() { foreach (Comment *comment, this->commentsInBlock) { comment->setFuzzyTimestamps(); } } void CommenterBlock::updateAvatarFollowStates() { foreach (Comment *comment, this->commentsInBlock) { comment->syncAvatarFollowState(); } } void CommenterBlock::updateShowAllLink(bool firstTry) { QString showAllString; if (allCommentsShown) { showAllString = this->reloadCommentsString; } else { if (firstTry) { showAllString = tr("Check for comments"); } else { showAllString = tr("Show all %1 comments") .arg(this->currentCommentCount); } } // Small trick to avoid a bug where the link stops having appropriate mouse pointer this->showAllCommentsLinkLabel->clear(); this->showAllCommentsLinkLabel->hide(); this->showAllCommentsLinkLabel->setText("" + showAllString + ""); this->showAllCommentsLinkLabel->show(); } /* * Disable the "check for comments" link when there's no valid comment URL, * by setting a clear message instead. Simply disabling the widget wouldn't * make the link look disabled. * */ void CommenterBlock::disableShowAllLink() { this->showAllCommentsLinkLabel->setText(tr("Comments are not available")); } void CommenterBlock::adjustCommentsWidth() { //commentsWidget->setMaximumWidth(commentsScrollArea->viewport()->width() - 2); } void CommenterBlock::adjustCommentsHeight() { if (this->commentsScrollArea->isVisible()) { this->commentsScrollArea->hide(); // this->commentsWidget->ensurePolished(); // Disabled for 1.3.1; using classic resize stuff this->commentsScrollArea->show(); } } void CommenterBlock::adjustCommentArea() { this->commentsWidget->ensurePolished(); // So .height() is valid int commentsWidgetHeight = qMax(this->commentsWidget->height(), 32); // 32 px min! (size of avatar) int goodScrollAreaHeight = qMin(commentsWidgetHeight, globalObj->getTimelineHeight()); // Not bigger than the window goodScrollAreaHeight += commentsScrollArea->frameWidth() * 2; // Account for the frame if (!parentPostStandalone) { // Minimum is set only when the post is not open as standalone this->commentsScrollArea->setMinimumHeight(goodScrollAreaHeight); } this->commentsScrollArea->setMaximumHeight(goodScrollAreaHeight); } void CommenterBlock::redrawComments() { foreach (Comment *comment, this->commentsInBlock) { comment->setCommentContents(); } } bool CommenterBlock::isFullMode() { return this->fullMode; } int CommenterBlock::getCommentCount() { return this->currentCommentCount; } Composer *CommenterBlock::getComposer() { return this->commentComposer; } /*****************************************************************************/ /*********************************** SLOTS ***********************************/ /*****************************************************************************/ void CommenterBlock::setMinimumMode() { this->commentComposer->hide(); this->toolsButton->hide(); statusInfoLabel->clear(); this->statusInfoLabel->hide(); this->commentButton->hide(); this->cancelButton->hide(); // Clear formatting options like bold or italic this->commentComposer->setCurrentCharFormat(QTextCharFormat()); // Clear "editing mode", restore stuff if (editingMode) { this->editingMode = false; this->editingCommentId.clear(); this->commentButton->setText(tr("Comment", // Button text back to "Comment" as usual "Infinitive verb")); // With exact same comment, so it's 1 entry } this->fullMode = false; } void CommenterBlock::setFullMode(QString initialText) { this->commentComposer->show(); this->toolsButton->show(); this->statusInfoLabel->show(); this->commentButton->show(); this->cancelButton->show(); this->commentComposer->setFocus(); if (!initialText.isEmpty()) { this->commentComposer->append(initialText); // Alternative way, would insert anywhere, has other problems // this->commentComposer->insertHtml(initialText); // Delete extra newline after the quote this->commentComposer->moveCursor(QTextCursor::End); // Otherwise could delete other characters this->commentComposer->textCursor().deletePreviousChar(); } this->fullMode = true; } void CommenterBlock::quoteComment(QString content) { this->setFullMode(content); // FIXME: connect to setFullMode(QString) directly? } void CommenterBlock::editComment(QString id, QString content) { // Avoid destroying a comment currently being composed! if (editingMode || fullMode) { QMessageBox::warning(this, tr("Error: Already composing"), tr("You can't edit a comment at this time, " "because another comment is already being composed.")); return; } this->editingMode = true; this->editingCommentId = id; setFullMode(); this->commentComposer->setHtml(content); this->commentButton->setText("Update"); this->statusInfoLabel->setText(tr("Editing comment")); } void CommenterBlock::requestAllComments() { emit allCommentsRequested(); // FIXME 1.3: could connect() to this signal directly } /* * Called when commentPosted() is emmited by PumpController, * which means comment posted (or updated) successfully. * */ void CommenterBlock::onPostingCommentOk() { // Clear info message this->statusInfoLabel->clear(); // Comment was added successfully, so we can re-enable things this->setEnabled(true); // Erase the text from the comment box... this->commentComposer->erase(); // Show the comment area, even if empty, in case comments are not reloaded ok this->showAllCommentsLinkLabel->show(); this->commentsScrollArea->show(); // and since we're done posting the comment, hide this setMinimumMode(); disconnect(pController, SIGNAL(commentPosted()), this, SLOT(onPostingCommentOk())); disconnect(pController, SIGNAL(commentPostingFailed()), this, SLOT(onPostingCommentFailed())); this->getAllCommentsTimer->start(3000); // Request comments after a delay } /* * Executed when commentPostingFailed() signal is received from PumpController * */ void CommenterBlock::onPostingCommentFailed() { qDebug() << "Posting the comment failed, re-enabling Commenter"; // Alert about the error this->statusInfoLabel->setText(tr("Posting comment failed.\n\nTry again.")); // Re-enable things, so user can try again this->setEnabled(true); this->commentComposer->setFocus(); disconnect(pController, SIGNAL(commentPostingFailed()), this, SLOT(onPostingCommentFailed())); disconnect(pController, SIGNAL(commentPosted()), this, SLOT(onPostingCommentOk())); } void CommenterBlock::sendComment() { qDebug() << "Commenter character count:" << commentComposer->textCursor().document()->characterCount(); // If there's some text in the comment, send it if (commentComposer->textCursor().document()->characterCount() > 1) { connect(pController, SIGNAL(commentPosted()), this, SLOT(onPostingCommentOk())); connect(pController, SIGNAL(commentPostingFailed()), this, SLOT(onPostingCommentFailed())); if (!editingMode) { this->statusInfoLabel->setText(tr("Sending comment...")); emit commentSent(commentComposer->toHtml()); } else { this->statusInfoLabel->setText(tr("Updating comment...")); emit commentUpdated(editingCommentId, commentComposer->toHtml()); } this->setDisabled(true); } else { this->statusInfoLabel->setText(tr("Comment is empty.")); qDebug() << "Can't post, comment is empty"; } } /* * Called by the QTimer * */ void CommenterBlock::scrollCommentsToBottom() { this->adjustCommentsWidth(); this->adjustCommentArea(); commentsScrollArea->verticalScrollBar()->triggerAction(QScrollBar::SliderToMaximum); } /*****************************************************************************/ /********************************* PROTECTED *********************************/ /*****************************************************************************/ void CommenterBlock::resizeEvent(QResizeEvent *event) { //qDebug() << "CommenterBlock::resizeEvent()" // << event->oldSize() << ">" << event->size(); this->adjustCommentsWidth(); this->redrawComments(); //this->commentsWidget->adjustSize(); //this->commentsScrollArea->adjustSize(); this->adjustCommentArea(); event->accept(); } dianara-v1.3.2/src/commenterblock.h000644 000764 000764 00000006724 12604246773 016664 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef COMMENTER_H #define COMMENTER_H #include #include #include #include #include #include #include #include #include "pumpcontroller.h" #include "globalobject.h" #include "composer.h" #include "asobject.h" #include "comment.h" class CommenterBlock : public QWidget { Q_OBJECT public: explicit CommenterBlock(PumpController *pumpController, GlobalObject *globalObject, QString parentAuthorId, bool parentStandalone, QWidget *parent = 0); ~CommenterBlock(); void clearComments(); void setComments(QVariantList commentsList, int commentCount); void appendComment(ASObject *object, bool justOne=false); void updateCommentFromObject(ASObject *object); void setCommentDeletedFromObject(ASObject *object); void updateFuzzyTimestamps(); void updateAvatarFollowStates(); void updateShowAllLink(bool firstTry=false); void disableShowAllLink(); void adjustCommentsWidth(); void adjustCommentsHeight(); void adjustCommentArea(); void redrawComments(); bool isFullMode(); int getCommentCount(); Composer *getComposer(); signals: void commentSent(QString commentText); void commentUpdated(QString commentId, QString commentText); void allCommentsRequested(); public slots: void setMinimumMode(); void setFullMode(QString initialText=""); void quoteComment(QString content); void editComment(QString id, QString content); void requestAllComments(); void onPostingCommentOk(); void onPostingCommentFailed(); void sendComment(); void scrollCommentsToBottom(); protected: virtual void resizeEvent(QResizeEvent *event); private: QVBoxLayout *mainLayout; QGridLayout *bottomLayout; QScrollArea *commentsScrollArea; QWidget *commentsWidget; QVBoxLayout *commentsLayout; QTimer *scrollToBottomTimer; QString reloadCommentsString; QLabel *showAllCommentsLinkLabel; QTimer *getAllCommentsTimer; // To get all comments after a delay Composer *commentComposer; QPushButton *toolsButton; QLabel *statusInfoLabel; QPushButton *commentButton; QPushButton *cancelButton; bool editingMode; QString editingCommentId; bool fullMode; QList commentsInBlock; int currentCommentCount; bool allCommentsShown; QString parentPostAuthorId; bool parentPostStandalone; PumpController *pController; GlobalObject *globalObj; }; #endif // COMMENTER_H dianara-v1.3.2/src/comment.cpp000644 000764 000764 00000044067 12611531351 015642 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "comment.h" Comment::Comment(PumpController *pumpController, GlobalObject *globalObject, ASObject *commentObject, QWidget *parent) : QFrame(parent) { this->pController = pumpController; this->globalObj = globalObject; commentObject->setParent(this); // reparent the passed object QSizePolicy sizePolicy; sizePolicy.setHeightForWidth(false); sizePolicy.setWidthForHeight(false); sizePolicy.setHorizontalPolicy(QSizePolicy::Ignored); sizePolicy.setVerticalPolicy(QSizePolicy::Maximum); // Previously QSizePolicy::Preferred this->setSizePolicy(sizePolicy); this->setMinimumSize(10, 10); // Ensure something's visible at any time this->setMaximumHeight(4096); this->commentId = commentObject->getId(); this->objectType = commentObject->getType(); this->commentAuthorId = commentObject->author()->getId(); if (commentAuthorId == pController->currentUserId()) { commentIsOwn = true; // Comment is ours! // Different frame style depending on whether the comment is ours or not this->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel); } else { commentIsOwn = false; this->setFrameStyle(QFrame::Raised | QFrame::StyledPanel); } this->commentIsDeleted = false; // Avatar pixmap avatarButton = new AvatarButton(commentObject->author(), this->pController, this->globalObj, QSize(32,32), this); QFont commentsFont; commentsFont.fromString(globalObj->getCommentsFont()); // Name, with ID as tooltip QFont metadataFont; metadataFont.setPointSize(metadataFont.pointSize() - 1); // 1 point less than default metadataFont.setBold(true); if (commentIsOwn) { metadataFont.setItalic(true); } fullNameLabel = new QLabel(commentObject->author()->getNameWithFallback()); fullNameLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); fullNameLabel->setFont(metadataFont); fullNameLabel->setToolTip(commentAuthorId); // Timestamps metadataFont.setBold(false); metadataFont.setItalic(true); timestampLabel = new HClabel("", this); timestampLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); timestampLabel->setWordWrap(false); // ON by default in HClabel timestampLabel->setFont(metadataFont); // Like and Delete "buttons" metadataFont.setBold(true); metadataFont.setItalic(false); likeLabel = new QLabel("*like*", this); likeLabel->setContextMenuPolicy(Qt::NoContextMenu); likeLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); likeLabel->setFont(metadataFont); likeLabel->setToolTip("" + tr("Like or unlike this comment")); connect(likeLabel, SIGNAL(linkActivated(QString)), this, SLOT(likeComment(QString))); quoteLabel = new QLabel("" + tr("Quote", "This is a verb, infinitive") + "", this); quoteLabel->setContextMenuPolicy(Qt::NoContextMenu); quoteLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); quoteLabel->setFont(metadataFont); quoteLabel->setToolTip("" + tr("Reply quoting this comment")); connect(quoteLabel, SIGNAL(linkHovered(QString)), this, SLOT(saveCommentSelectedText())); connect(quoteLabel, SIGNAL(linkActivated(QString)), this, SLOT(quoteComment())); editLabel = new QLabel("" + tr("Edit") + "", this); editLabel->setContextMenuPolicy(Qt::NoContextMenu); editLabel->setAlignment(Qt::AlignTop | Qt::AlignRight); editLabel->setFont(metadataFont); editLabel->setToolTip("" + tr("Modify this comment")); connect(editLabel, SIGNAL(linkActivated(QString)), this, SLOT(editComment())); deleteLabel = new QLabel("" + tr("Delete") + "", this); deleteLabel->setContextMenuPolicy(Qt::NoContextMenu); deleteLabel->setAlignment(Qt::AlignTop | Qt::AlignRight); deleteLabel->setFont(metadataFont); deleteLabel->setToolTip("" + tr("Erase this comment")); connect(deleteLabel, SIGNAL(linkActivated(QString)), this, SLOT(deleteComment())); // The likes count likesCountLabel = new HClabel("", this); likesCountLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); metadataFont.setBold(false); likesCountLabel->setFont(metadataFont); // Main content, the comment itself contentLabel = new QLabel(this); contentLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); contentLabel->setFont(commentsFont); contentLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); // To help screen readers add Qt::TextSelectableByKeyboard flag too; // Not adding for now, because it doesn't really help much. This will work better with Qt5 contentLabel->setWordWrap(true); contentLabel->setOpenExternalLinks(true); contentLabel->setTextFormat(Qt::RichText); contentLabel->setSizePolicy(sizePolicy); contentLabel->setMaximumHeight(4096); connect(contentLabel, SIGNAL(linkHovered(QString)), this, SLOT(showUrlInfo(QString))); // This is used to draw a colored vertical line, as a hint hintWidget = new QWidget(this); hintWidget->hide(); // Layout leftLayout = new QVBoxLayout(); leftLayout->setContentsMargins(0, 0, 0, 0); leftLayout->setAlignment(Qt::AlignLeft); leftLayout->addWidget(avatarButton, 0, Qt::AlignLeft | Qt::AlignTop); leftLayout->addWidget(likesCountLabel, 0, Qt::AlignHCenter | Qt::AlignTop); leftLayout->addStretch(); rightTopLayout = new QHBoxLayout(); rightTopLayout->addWidget(fullNameLabel, 0, Qt::AlignLeft); rightTopLayout->addWidget(timestampLabel, 0, Qt::AlignLeft); rightTopLayout->addWidget(likeLabel, 0, Qt::AlignLeft); rightTopLayout->addWidget(quoteLabel, 0, Qt::AlignLeft); rightTopLayout->addStretch(1); if (commentIsOwn) { rightTopLayout->addWidget(editLabel, 0, Qt::AlignRight); rightTopLayout->addWidget(deleteLabel, 0, Qt::AlignRight); } else { // Since these widgets are initialized with a parent, hide them when not needed editLabel->hide(); deleteLabel->hide(); } rightLayout = new QVBoxLayout(); rightLayout->addLayout(rightTopLayout, 0); rightLayout->addSpacing(4); // 4px vertical space separation rightLayout->addWidget(contentLabel, 1, Qt::AlignTop); rightLayout->addSpacing(1); // and 1px more as margin mainLayout = new QHBoxLayout(); mainLayout->setContentsMargins(2, 2, 2, 2); mainLayout->addWidget(hintWidget); mainLayout->addLayout(leftLayout); mainLayout->addLayout(rightLayout); this->setLayout(mainLayout); this->updateDataFromObject(commentObject); qDebug() << "Comment created" << this->commentId; } Comment::~Comment() { qDebug() << "Comment destroyed" << this->commentId; } /* * Fill in or replace data such as timestamps, title, contents, * number of likes and shares, etc, from object data * */ void Comment::updateDataFromObject(ASObject *object) { // Timestamps this->createdAt = object->getCreatedAt(); this->updatedAt = object->getUpdatedAt(); QString timestampTooltip = tr("Posted on %1") .arg(Timestamp::localTimeDate(createdAt)); if (createdAt != updatedAt) { timestampTooltip.append("
              " + tr("Modified on %1") .arg(Timestamp::localTimeDate(updatedAt))); } timestampLabel->setToolTip(timestampTooltip); // Precise time on tooltip this->setFuzzyTimestamps(); this->setLikesCount(object->getLikesCount(), object->getLastLikesList()); if (object->isLiked() == "true") { commentIsLiked = true; } else { commentIsLiked = false; } this->fixLikeLabelText(); // The comment itself this->commentOriginalText = object->getContent(); pendingImagesList = MiscHelpers::htmlWithReplacedImages(commentOriginalText, 128); // Arbitrary (initial) width pendingImagesList.removeFirst(); // First one is the HTML with images replaced this->getPendingImages(); this->setCommentContents(); } void Comment::fixLikeLabelText() { if (commentIsLiked) { this->likeLabel->setText("" + tr("Unlike") +""); } else { this->likeLabel->setText("" + tr("Like") +""); } } void Comment::setLikesCount(QString count, QVariantList namesVariantList) { // FIXME: count should be int if (count != "0") { QString likesString = ASObject::personStringFromList(namesVariantList, count.toInt()); if (count == "1") { likesString = tr("%1 likes this comment", "Singular: %1=name of just " "1 person").arg(likesString); } else // several people { likesString = tr("%1 like this comment", "Plural: %1=list of people like John, " "Jane, Smith").arg(likesString); } likesCountLabel->setBaseText(QString::fromUtf8("\342\231\245") // Heart symbol + QString(" %1").arg(count)); // set tooltip as HTML, so it gets wordwrapped likesCountLabel->setToolTip("" + likesString); likesCountLabel->show(); } else { likesCountLabel->clear(); likesCountLabel->setToolTip(""); likesCountLabel->hide(); } } void Comment::setFuzzyTimestamps() { QString timestamp = Timestamp::fuzzyTime(createdAt); if (createdAt != updatedAt) // Comment has been edited { timestamp.prepend("**"); // FIXME, somehow show edit time } this->timestampLabel->setBaseText(timestamp); } void Comment::syncAvatarFollowState() { this->avatarButton->syncFollowState(); } /* * Set the contents of the comment, parsing images, etc. * */ void Comment::setCommentContents() { int imageWidth = this->contentLabel->width() - 20; // Kinda TMP QStringList commentImageList = MiscHelpers::htmlWithReplacedImages(commentOriginalText, imageWidth); QString commentContents = commentImageList.takeAt(0); // Comment's HTML with images replaced this->contentLabel->setText(commentContents); } void Comment::onResize() { contentLabel->ensurePolished(); int height = contentLabel->heightForWidth(contentLabel->width()); this->contentLabel->setMinimumHeight(height); this->contentLabel->setMaximumHeight(height); } void Comment::getPendingImages() { if (!pendingImagesList.isEmpty()) { foreach (QString imageUrl, pendingImagesList) { pController->enqueueImageForDownload(imageUrl); } connect(pController, SIGNAL(imageStored(QString)), this, SLOT(redrawImages(QString))); } } QString Comment::getObjectId() { return this->commentId; } /* * A thin line on left side as hint to indicate comment is yours * or from the author of the parent post * */ void Comment::setHint(QString color) { if (color.isEmpty()) { color = "palette(highlight)"; } // Transparent to your color to transparent gradient QString css = QString("QWidget " "{ background-color: " " qlineargradient(spread:pad, " " x1:0, y1:0, x2:1, y2:0, " " stop:0 rgba(0, 0, 0, 0), " " stop:0.25 %1, stop:0.75 %1, " " stop:1 rgba(0, 0, 0, 0) ); " "}").arg(color); this->hintWidget->setStyleSheet(css); this->hintWidget->setFixedWidth(4); // 4 px this->hintWidget->show(); } void Comment::setCommentDeleted(QString deletedTime) { if (commentIsDeleted) { return; // Was already deleted, so just ignore } this->commentIsDeleted = true; if (!this->commentOriginalText.isEmpty()) { this->commentOriginalText.prepend("
              "); // -------- } this->commentOriginalText.prepend("
              " + deletedTime + "
              "); qDebug() << "This comment was deleted on" << deletedTime; this->setCommentContents(); this->onResize(); this->setDisabled(true); } /****************************************************************************/ /******************************** SLOTS *************************************/ /****************************************************************************/ void Comment::likeComment(QString clickedLink) { if (clickedLink == "like://") { commentIsLiked = true; } else // unlike:// { commentIsLiked = false; } this->pController->likePost(this->commentId, this->objectType, this->commentAuthorId, this->commentIsLiked); this->fixLikeLabelText(); } /* * This will be called when hovering the "Quote" link * * Store the selected text, so it can be used when actually quoting * */ void Comment::saveCommentSelectedText() { this->commentSelectedText = contentLabel->selectedText(); // TMP / FIXME: issues here; the signal is not emitted every time qDebug() << "### comment SELECTED TEXT: " << contentLabel->selectedText(); } /* * Take the contents of a comment and put them as a quote block * in the comment composer. * * If some text has been selected, saveCommentSelectedText() * will have it stored in a variable, used here. * */ void Comment::quoteComment() { QString quotedComment; if (commentSelectedText.isEmpty()) { // Quote full comment quotedComment = MiscHelpers::quotedText(this->fullNameLabel->text(), this->contentLabel->text()); } else { // Quote the selection only commentSelectedText.prepend("[...] "); commentSelectedText.append(" [...]"); quotedComment = MiscHelpers::quotedText(this->fullNameLabel->text(), this->commentSelectedText); } commentSelectedText.clear(); emit commentQuoteRequested(quotedComment); } void Comment::editComment() { emit commentEditRequested(this->commentId, this->commentOriginalText); } void Comment::deleteComment() { int confirmation = QMessageBox::question(this, tr("WARNING: Delete comment?"), tr("Are you sure you want to delete this comment?"), tr("&Yes, delete it"), tr("&No"), "", 1, 1); if (confirmation == 0) { qDebug() << "Deleting comment" << this->commentId; this->pController->deletePost(this->commentId, this->objectType); QString timeNow = QDateTime::currentDateTimeUtc().toString(Qt::ISODate); this->setCommentDeleted(ASObject::makeDeletedOnString(timeNow)); } else { qDebug() << "Confirmation cancelled, not deleting the comment"; } } /* * Show the URL of a link hovered in a comment * */ void Comment::showUrlInfo(QString url) { if (!url.isEmpty()) { this->pController->showTransientMessage(url); qDebug() << "Link hovered in comment:" << url; } else { this->pController->showTransientMessage(""); } } /* * Redraw comment contents after receiving downloaded images * */ void Comment::redrawImages(QString imageUrl) { if (pendingImagesList.contains(imageUrl)) { this->pendingImagesList.removeAll(imageUrl); if (pendingImagesList.isEmpty()) // If there are no more, disconnect { disconnect(pController, SIGNAL(imageStored(QString)), this, SLOT(redrawImages(QString))); setCommentContents(); } } } /****************************************************************************/ /****************************** PROTECTED ***********************************/ /****************************************************************************/ /* * Ensure url info in statusbar is hidden when the mouse leaves the comment * */ void Comment::leaveEvent(QEvent *event) { this->pController->showTransientMessage(""); event->accept(); } void Comment::resizeEvent(QResizeEvent *event) { //qDebug() << "Comment::resizeEvent()" // << event->oldSize() << ">" << event->size(); this->onResize(); event->accept(); } dianara-v1.3.2/src/imageviewer.h000664 000764 000764 00000004415 12451104253 016143 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef IMAGEVIEWER_H #define IMAGEVIEWER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mischelpers.h" class ImageViewer : public QWidget { Q_OBJECT public: explicit ImageViewer(QString fileURI, QString title, QString suggestedFN, bool isAnimated, QWidget *parent = 0); ~ImageViewer(); void createContextMenu(); signals: public slots: void saveImage(); void restartAnimation(); protected: virtual void closeEvent(QCloseEvent *event); virtual void hideEvent(QHideEvent *event); virtual void resizeEvent(QResizeEvent *event); virtual void contextMenuEvent(QContextMenuEvent *event); private: QVBoxLayout *mainLayout; QLabel *imageLabel; QHBoxLayout *buttonsLayout; QPushButton *saveImageButton; QPushButton *restartButton; QLabel *infoLabel; QPushButton *closeButton; QMenu *contextMenu; QAction *saveAction; QAction *closeAction; QPixmap originalPixmap; QString originalFileURI; bool imageIsAnimated; QMovie *movie; QString suggestedFilename; }; #endif // IMAGEVIEWER_H dianara-v1.3.2/src/imageviewer.cpp000644 000764 000764 00000020527 12573333661 016512 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "imageviewer.h" ImageViewer::ImageViewer(QString fileURI, QString title, QString suggestedFN, bool isAnimated, QWidget *parent) : QWidget(parent) { this->setMinimumSize(460, 320); this->suggestedFilename = suggestedFN; this->imageIsAnimated = isAnimated; fileURI.remove(0, 7); // remove "image:/" from filename URI this->originalFileURI = fileURI; originalPixmap = QPixmap(originalFileURI); QString resolution = QString("%1x%2") .arg(originalPixmap.width()) .arg(originalPixmap.height()); QString imageDetails = resolution + ", " + MiscHelpers::fileSizeString(originalFileURI); if (title.isEmpty()) { title = "--"; } this->setWindowTitle(tr("Image") + ": " + title + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("folder-image", QIcon(":/images/attached-image.png"))); imageLabel = new QLabel(this); imageLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); imageLabel->setAlignment(Qt::AlignCenter); saveImageButton = new QPushButton(QIcon::fromTheme("document-save-as", QIcon(":/images/button-save.png")), tr("&Save As..."), this); connect(saveImageButton, SIGNAL(clicked()), this, SLOT(saveImage())); if (imageIsAnimated) { this->movie = new QMovie(this); movie->setFileName(this->originalFileURI); movie->start(); imageLabel->setMovie(movie); restartButton = new QPushButton(QIcon::fromTheme("media-playback-start", QIcon(":/images/menu-refresh.png")), tr("&Restart Animation"), this); connect(restartButton, SIGNAL(clicked()), this, SLOT(restartAnimation())); } infoLabel = new QLabel(imageDetails, this); infoLabel->setAlignment(Qt::AlignCenter); closeButton = new QPushButton(QIcon::fromTheme("window-close", QIcon(":/images/button-close.png")), tr("&Close"), this); connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); this->createContextMenu(); // Layout buttonsLayout = new QHBoxLayout(); buttonsLayout->addWidget(saveImageButton); if (imageIsAnimated) { buttonsLayout->addWidget(restartButton); } buttonsLayout->addWidget(infoLabel, 1); buttonsLayout->addWidget(closeButton); mainLayout = new QVBoxLayout(); mainLayout->addWidget(imageLabel); mainLayout->addLayout(buttonsLayout); this->setLayout(mainLayout); // Set initial window size according to image size and desktop (screen) size QDesktopWidget desktopWidget; int windowWidth = qMin(originalPixmap.width() + 100, // Some margin for the window itself desktopWidget.availableGeometry().width()); int windowHeight = qMin(originalPixmap.height() + 100, desktopWidget.availableGeometry().height()); this->resize(windowWidth, windowHeight); qDebug() << "ImageViewer created"; } ImageViewer::~ImageViewer() { qDebug() << "ImageViewer destroyed"; } void ImageViewer::createContextMenu() { this->saveAction = new QAction(QIcon::fromTheme("document-save-as", QIcon(":/images/button-save.png")), tr("Save Image..."), this); saveAction->setShortcut(QKeySequence("Ctrl+S")); connect(saveAction, SIGNAL(triggered()), this, SLOT(saveImage())); this->closeAction = new QAction(QIcon::fromTheme("window-close", QIcon(":/images/button-close.png")), tr("Close Viewer"), this); closeAction->setShortcut(QKeySequence(Qt::Key_Escape)); connect(closeAction, SIGNAL(triggered()), this, SLOT(close())); this->contextMenu = new QMenu("imageViewerMenu", this); contextMenu->addAction(saveAction); contextMenu->addAction(closeAction); // Make the shortcuts work this->addAction(saveAction); this->addAction(closeAction); } /****************************************************************************/ /************************************ SLOTS *********************************/ /****************************************************************************/ void ImageViewer::saveImage() { bool savedCorrectly; QString filename; filename = QFileDialog::getSaveFileName(this, tr("Save Image As..."), QDir::homePath() + "/" + suggestedFilename, tr("Image files") + " (*.jpg *.png);;" + tr("All files") + " (*)"); if (!filename.isEmpty()) { // Save pixmap from original file savedCorrectly = QPixmap(this->originalFileURI).save(filename); // FIXME: change this to directly copy the unmodified original instead if (!savedCorrectly) { QMessageBox::warning(this, tr("Error saving image"), tr("There was a problem while saving %1.\n\n" "Filename should end in .jpg " "or .png extensions.").arg(filename)); } } } void ImageViewer::restartAnimation() { if (imageIsAnimated) { this->movie->stop(); this->movie->start(); } } /****************************************************************************/ /********************************** PROTECTED *******************************/ /****************************************************************************/ void ImageViewer::closeEvent(QCloseEvent *event) { qDebug() << "ImageViewer::closeEvent(); hiding and destroying widget!"; event->ignore(); this->hide(); this->deleteLater(); } void ImageViewer::hideEvent(QHideEvent *event) { qDebug() << "ImageViewer::hideEvent()"; event->accept(); } void ImageViewer::resizeEvent(QResizeEvent *event) { qDebug() << "ImageViewer::resizeEvent()"; int width = qMin(imageLabel->width() - 16, // 16px as margin originalPixmap.width()); int height = qMin(imageLabel->height() - 16, originalPixmap.height()); // FIXME: maybe for animated images (usually small GIFs) // the image could be allowed to grow to > 1x if (!this->imageIsAnimated) { imageLabel->setPixmap(originalPixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { QSize movieSize(originalPixmap.width(), originalPixmap.height()); movieSize.scale(width, height, Qt::KeepAspectRatio); movie->setScaledSize(movieSize); } event->accept(); } void ImageViewer::contextMenuEvent(QContextMenuEvent *event) { this->contextMenu->exec(event->globalPos()); } dianara-v1.3.2/src/minorfeed.cpp000644 000764 000764 00000054443 12604556320 016154 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "minorfeed.h" MinorFeed::MinorFeed(PumpController::requestTypes minorFeedType, PumpController *pumpController, GlobalObject *globalObject, FilterChecker *filterChecker, QWidget *parent) : QFrame(parent) { this->feedType = minorFeedType; this->pController = pumpController; this->globalObj = globalObject; this->fChecker = filterChecker; this->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); this->setContentsMargins(0, 0, 0, 0); // Layout for the items itemsLayout = new QVBoxLayout(); itemsLayout->setContentsMargins(0, 0, 0, 0); itemsLayout->setSpacing(1); // Very small spacing itemsLayout->setAlignment(Qt::AlignTop); // Separator frame, to mark where new items end separatorFrame = new QFrame(this); separatorFrame->setFrameStyle(QFrame::HLine); separatorFrame->setMinimumHeight(28); separatorFrame->setContentsMargins(0, 8, 0, 8); separatorFrame->hide(); // Button to get newer items (pending stuff) getPendingButton = new QPushButton(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), "*get pending activities*", this); getPendingButton->setFlat(true); connect(getPendingButton, SIGNAL(clicked()), this, SLOT(updateFeed())); getPendingButton->hide(); // Button to get more items (older stuff) getOlderButton = new QPushButton(QIcon::fromTheme("list-add", QIcon(":/images/list-add.png")), tr("Older Activities"), this); getOlderButton->setFlat(true); getOlderButton->setToolTip(tr("Get previous minor activities")); connect(getOlderButton, SIGNAL(clicked()), this, SLOT(getMoreActivities())); // Set disabled initially; will be enabled when contents are set getOlderButton->setDisabled(true); // Main layout mainLayout = new QVBoxLayout(); mainLayout->setContentsMargins(1, 1, 1, 1); mainLayout->setAlignment(Qt::AlignTop); mainLayout->addWidget(getPendingButton); mainLayout->addLayout(itemsLayout); mainLayout->addWidget(getOlderButton); this->setLayout(mainLayout); // Demo activity, stating that there's nothing to show QVariantMap demoGenerator; demoGenerator.insert("displayName", "Dianara"); QVariantMap demoActivityMap; demoActivityMap.insert("published", QDateTime::currentDateTimeUtc() .toString(Qt::ISODate)); demoActivityMap.insert("generator", demoGenerator); demoActivityMap.insert("content", tr("There are no activities to show yet.")); ASActivity *demoActivity = new ASActivity(demoActivityMap, this); MinorFeedItem *demoFeedItem = new MinorFeedItem(demoActivity, false, // Not highlighted by filter this->pController, this->globalObj, this); demoFeedItem->setItemAsNew(false, false); // Do not inform the feed this->itemsLayout->addWidget(demoFeedItem); this->itemsInFeed.append(demoFeedItem); this->firstLoad = true; this->gettingNew = true; // First time should be true this->unreadItemsCount = 0; this->highlightedItemsCount = 0; this->pendingToReceiveNextTime = 0; // Remember what was the newest activity last time QSettings settings; settings.beginGroup("MinorFeedStates"); switch (this->feedType) { case PumpController::MinorFeedMainRequest: this->previousNewestActivityId = settings .value("previousNewestItemIdMain") .toString(); this->fullFeedItemCount = settings.value("totalItemsMain").toInt(); this->feedBatchItemCount = 50; break; case PumpController::MinorFeedDirectRequest: this->previousNewestActivityId = settings .value("previousNewestItemIdDirect") .toString(); this->fullFeedItemCount = settings.value("totalItemsDirect").toInt(); this->feedBatchItemCount = 20; break; case PumpController::MinorFeedActivityRequest: this->previousNewestActivityId = settings .value("previousNewestItemIdActivity") .toString(); this->fullFeedItemCount = settings.value("totalItemsActivity").toInt(); this->feedBatchItemCount = 20; break; default: qDebug() << "MinorFeed created with wrong type:" << this->feedType; } settings.endGroup(); // Sync all avatar's follow state when there are changes in the Following list connect(pController, SIGNAL(followingListChanged()), this, SLOT(updateAvatarFollowStates())); qDebug() << "MinorFeed created"; } MinorFeed::~MinorFeed() { QSettings settings; settings.beginGroup("MinorFeedStates"); switch (this->feedType) { case PumpController::MinorFeedMainRequest: settings.setValue("previousNewestItemIdMain", this->previousNewestActivityId); settings.setValue("totalItemsMain", this->fullFeedItemCount); break; case PumpController::MinorFeedDirectRequest: settings.setValue("previousNewestItemIdDirect", this->previousNewestActivityId); settings.setValue("totalItemsDirect", this->fullFeedItemCount); break; case PumpController::MinorFeedActivityRequest: settings.setValue("previousNewestItemIdActivity", this->previousNewestActivityId); settings.setValue("totalItemsActivity", this->fullFeedItemCount); break; default: qDebug() << "MinorFeed destructor: feed type was wrong"; } settings.endGroup(); qDebug() << "MinorFeed destroyed; Type:" << this->feedType; } void MinorFeed::clearContents() { qDebug() << "MinorFeed::clearContents()"; foreach (MinorFeedItem *feedItem, itemsInFeed) { this->itemsLayout->removeWidget(feedItem); delete feedItem; } itemsInFeed.clear(); itemsLayout->removeWidget(separatorFrame); separatorFrame->hide(); } /* * Remove oldest items, to avoid ever-increasing memory usage * Called after updating the feed, only when getting newer items * * At the very least, keep as many items as were received in last update * */ void MinorFeed::removeOldItems(int minimumToKeep) { int maxItems = qMax(this->feedBatchItemCount * 2, // TMP FIXME minimumToKeep); if (itemsInFeed.count() <= maxItems) { // Not too many items yet return; } int itemCounter = 0; int deletedCounter = 0; // tmp, TESTS (EXTRALOGGING) foreach (MinorFeedItem *feedItem, itemsInFeed) { if (itemCounter >= maxItems) { if (!feedItem->isNew()) // Don't remove if it's unread (optional?) { this->itemsLayout->removeWidget(feedItem); this->itemsInFeed.removeOne(feedItem); delete feedItem; ++deletedCounter; // tmp, TESTS (EXTRALOGGING) } } ++itemCounter; } // Update "next" link manually, based on the last item present in the feed QByteArray lastItemId = itemsInFeed.last()->getActivityId().toLocal8Bit(); lastItemId = lastItemId.toPercentEncoding(); // Needs to be percent-encoded this->nextLink = this->pController->getFeedApiUrl(this->feedType) + "?before=" + lastItemId; #ifdef EXTRALOGGING // tmp, TESTS - debugging via log this->globalObj->logMessage(QString(">>> %1 ITEMS IN '%2' AFTER CLEANUP; " "%3 CAME, %4 REMOVED - Next: %5") .arg(itemsInFeed.count()) .arg(PumpController::getFeedNameAndPath(this->feedType).first()) .arg(minimumToKeep) .arg(deletedCounter) .arg(this->nextLink)); #endif } void MinorFeed::insertSeparator(int position) { this->itemsLayout->insertWidget(position, this->separatorFrame); this->separatorFrame->show(); } void MinorFeed::markAllAsRead() { foreach (MinorFeedItem *feedItem, itemsInFeed) { feedItem->setItemAsNew(false, // Mark as not new false); // Don't inform the feed } this->unreadItemsCount = 0; this->highlightedItemsCount = 0; } /* * Update fuzzy timestamps in all items * */ void MinorFeed::updateFuzzyTimestamps() { foreach (MinorFeedItem *feedItem, itemsInFeed) { feedItem->setFuzzyTimeStamp(); } } void MinorFeed::syncActivityWithTimelines(ASActivity *activity) { //////////////////////////////////////////////////////////// An edited post if (activity->getVerb() == "update") { if (ASObject::canDisplayObject(activity->object()->getType())) { emit objectUpdated(activity->object()); } } //////////////////////////////////////////// A new comment posted to a post if (activity->getVerb() == "post") { ASObject *activityObject = activity->object(); // Check if the object has proper author info; if not, use activity's author if (activityObject->author()->getId().isEmpty()) { activityObject->updateAuthorFromPerson(activity->author()); } // FIXME: this needs some checking... // v1.3.0b1+11: seems OK emit objectReplyAdded(activityObject); } ////////////////////////////////////////////////////////////// A liked post if (activity->getVerb() == "favorite" || activity->getVerb() == "like") { // FIXME: handle liking of comments (1.3.3) if (ASObject::canDisplayObject(activity->object()->getType())) { emit objectLiked(activity->object()->getId(), activity->object()->getType(), activity->author()->getId(), activity->author()->getNameWithFallback(), activity->author()->getUrl()); } } /////////////////////////////////////////////////////////// An unliked post if (activity->getVerb() == "unfavorite" || activity->getVerb() == "unlike") { // FIXME: handle unliking of comments (1.3.3) if (ASObject::canDisplayObject(activity->object()->getType())) { emit objectUnliked(activity->object()->getId(), activity->object()->getType(), activity->author()->getId()); } } //////////////////////////////////////////////////////////// A deleted post if (activity->getVerb() == "delete") { if (ASObject::canDisplayObject(activity->object()->getType())) { emit objectDeleted(activity->object()); } } // Sync "follow" and "stop-following" } /******************************************************************************/ /******************************************************************************/ /********************************** SLOTS *************************************/ /******************************************************************************/ /******************************************************************************/ /* * Get the latest activities * */ void MinorFeed::updateFeed() { this->gettingNew = true; this->getOlderButton->setDisabled(true); this->pController->getFeed(this->feedType, 200, // Maximum item count allowed by API this->prevLink); } /* * Get additional older activities * */ void MinorFeed::getMoreActivities() { this->gettingNew = false; this->getOlderButton->setDisabled(true); this->pController->getFeed(this->feedType, this->feedBatchItemCount, this->nextLink); } void MinorFeed::setFeedContents(QVariantList activitiesList, QString previous, QString next, int totalItemCount) { if (firstLoad) { this->prevLink = previous; this->nextLink = next; this->clearContents(); } else { if (gettingNew) { if (!previous.isEmpty()) { this->prevLink = previous; } } else { if (!next.isEmpty()) { this->nextLink = next; } } } qDebug() << "Current MinorFeed links:" << this->prevLink << this->nextLink; int activitiesListSize = activitiesList.size(); int totalItemDifference = -1; // TMP FIXME; for cases where it's unknown if (gettingNew) { // Check how many new items we should we expecting totalItemDifference = totalItemCount - this->fullFeedItemCount; this->fullFeedItemCount = totalItemCount; qDebug() << "MinorFeed::setFeedContents(); Should load" << totalItemDifference << "items this time ###"; // Check how many more items need to be received, if more than max are pending pendingToReceiveNextTime += totalItemDifference; pendingToReceiveNextTime -= activitiesListSize; if (pendingToReceiveNextTime > 0) { if (firstLoad) { // The difference in pending items is in the older activities, so doesn't count this->pendingToReceiveNextTime = 0; // TODO: On first load, could display the "pending" number at the "older" button } else { // Button at the top to fetch the pending activities, even more new stuff this->getPendingButton->setText(tr("Get %1 newer", "As in: Get 3 newer (activities)") .arg(pendingToReceiveNextTime)); this->getPendingButton->show(); } } else { this->pendingToReceiveNextTime = 0; // In case it was less than 0 this->getPendingButton->hide(); } } // Remove the separator line itemsLayout->removeWidget(separatorFrame); separatorFrame->hide(); int newItemsCount = 0; int newHighlightedItemsCount = 0; int newFilteredItemsCount = 0; bool itemIsNew; bool itemHighlightedByFilter; bool allNewItemsCounted = false; QString newestActivityId; // To store the activity ID for the newest item in the feed // so we can know how many new items we receive next time int insertedItemsCount = 0; bool needToInsertSeparator = false; QList activitiesToSync; foreach (QVariant activityVariant, activitiesList) { itemIsNew = false; ASActivity *activity = new ASActivity(activityVariant.toMap(), this); int filtered = fChecker->validateActivity(activity); // If there is no reason to filter out the item, add it to the feed if (filtered != FilterChecker::FilterOut) { // Store activities in reverse order, for later processing (sync with TL) activitiesToSync.prepend(activity); // Determine which activities are new if (newestActivityId.isEmpty()) // Only first time, for newest item { if (gettingNew) { newestActivityId = activity->getId(); } else { newestActivityId = this->previousNewestActivityId; allNewItemsCounted = true; } } // Determine if this item is new, or if not anymore if (!allNewItemsCounted) { if (activity->getId() == this->previousNewestActivityId) { allNewItemsCounted = true; if (newItemsCount > 0) { needToInsertSeparator = true; } } else { // If activity is not ours, add it to the count if (activity->author()->getId() != pController->currentUserId()) { ++newItemsCount; itemIsNew = true; } } } if (filtered == FilterChecker::Highlight) // kinda TMP { itemHighlightedByFilter = true; } else { itemHighlightedByFilter = false; } MinorFeedItem *newFeedItem = new MinorFeedItem(activity, itemHighlightedByFilter, this->pController, this->globalObj, this); newFeedItem->setItemAsNew(itemIsNew, false); // Don't inform the feed if (itemIsNew) { connect(newFeedItem, SIGNAL(itemRead(bool)), this, SLOT(decreaseNewItemsCount(bool))); if (newFeedItem->getItemHighlightType() != -1) { ++newHighlightedItemsCount; } } if (gettingNew) { if (needToInsertSeparator) // ------- { this->insertSeparator(insertedItemsCount); ++insertedItemsCount; needToInsertSeparator = false; } this->itemsLayout->insertWidget(insertedItemsCount, newFeedItem); this->itemsInFeed.insert(insertedItemsCount, newFeedItem); ++insertedItemsCount; } else // Not new, getting 'older', so add at the bottom { this->itemsLayout->addWidget(newFeedItem); this->itemsInFeed.append(newFeedItem); } } else { ++newFilteredItemsCount; // Since the item is not added to the feed, we need to delete the activity delete activity; // FIXME: should not delete, just hide } } // End foreach // The first time stuff is received from the server, there's no need to sync if (!firstLoad) { // Activities in activitiesToSync are stored in reverse foreach (ASActivity *activity, activitiesToSync) { // Send notifications to update objects in the timelines this->syncActivityWithTimelines(activity); } } // If there were new items, and not already added, add separator: ------- if (newItemsCount > 0 && this->separatorFrame->isHidden()) { this->insertSeparator(insertedItemsCount); } if (!newestActivityId.isEmpty()) // If some items were received should be valid { this->previousNewestActivityId = newestActivityId; } this->unreadItemsCount += newItemsCount; this->highlightedItemsCount += newHighlightedItemsCount; qDebug() << "Minor feed updated"; if (gettingNew) // not when getting more, older ones { emit newItemsCountChanged(this->unreadItemsCount, this->highlightedItemsCount); emit newItemsReceived(this->feedType, newItemsCount, newHighlightedItemsCount, newFilteredItemsCount, this->pendingToReceiveNextTime); qDebug() << "New items:" << newItemsCount << "; New highlighted:" << newHighlightedItemsCount; // Clean up, keeping at least the ones that were just received if (activitiesListSize > 0) // but only if something was received { this->removeOldItems(activitiesListSize); } } else { emit newItemsReceived(this->feedType, activitiesListSize, // Actual number of received items -1, -1, -1); // -1 highlighted, filtered and pending // means these are old items } this->getOlderButton->setEnabled(true); firstLoad = false; } void MinorFeed::decreaseNewItemsCount(bool wasHighlighted) { --unreadItemsCount; if (wasHighlighted) { --highlightedItemsCount; } emit newItemsCountChanged(unreadItemsCount, highlightedItemsCount); } void MinorFeed::updateAvatarFollowStates() { foreach (MinorFeedItem *feedItem, itemsInFeed) { feedItem->syncAvatarFollowState(); } } dianara-v1.3.2/src/accountdialog.cpp000664 000764 000764 00000040027 12613256612 017014 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "accountdialog.h" AccountDialog::AccountDialog(PumpController *pumpController, QWidget *parent) : QWidget(parent) { this->setWindowTitle(tr("Account Configuration") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("dialog-password", QIcon(":/images/button-password.png"))); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::ApplicationModal); this->setMinimumSize(640, 520); this->pController = pumpController; connect(pController, SIGNAL(openingAuthorizeURL(QUrl)), this, SLOT(showAuthorizationURL(QUrl))); connect(pController, SIGNAL(authorizationStatusChanged(bool)), this, SLOT(showAuthorizationStatus(bool))); QFont helpFont; helpFont.setPointSize(helpFont.pointSize() - 1); this->helpMessage1Label = new QLabel(tr("First, enter your Webfinger ID, " "your pump.io address.") + "
              " + tr("Your address looks like " "username@pumpserver.org, and " "you can find it in your profile, " "in the web interface.") + "
              " + tr("If your profile is at " "https://pump.example/yourname, then " "your address is yourname@pump.example") + "

              " + tr("If you don't have an account yet, " "you can sign up for one at %1. " "This link will take you to a " "random public server.", "1=link to website") .arg("" "pump.io/tryit.html") + "

              " + tr("If you need help: %1") .arg("" + tr("Pump.io User Guide") + ""), this); helpMessage1Label->setWordWrap(true); helpMessage1Label->setFont(helpFont); helpMessage1Label->setOpenExternalLinks(true); userIDIconLabel = new QLabel(this); userIDIconLabel->setPixmap(QIcon::fromTheme("preferences-desktop-user", QIcon(":/images/no-avatar.png")) .pixmap(64,64) .scaledToWidth(64, Qt::SmoothTransformation)); userIdLabel = new QLabel("" + tr("Your Pump.io address:") + "", this); userIdLineEdit = new QLineEdit(this); QString userIdTooltip = tr("Your address, like username@pumpserver.org"); userIdLineEdit->setPlaceholderText(userIdTooltip); userIdLineEdit->setToolTip("" + userIdTooltip); // HTMLized for wordwrap getVerifierButton = new QPushButton(QIcon::fromTheme("object-unlocked"), tr("Get &Verifier Code"), this); getVerifierButton->setToolTip("" + tr("After clicking this button, a web " "browser will open, requesting " "authorization for Dianara")); connect(getVerifierButton, SIGNAL(clicked()), this, SLOT(askForToken())); this->separatorLine = new QFrame(this); separatorLine->setFrameStyle(QFrame::HLine); this->helpMessage2Label = new QLabel(tr("Once you have authorized Dianara " "from your Pump server web " "interface, you'll receive a code " "called VERIFIER.\n" "Copy it and paste it into the " "field below.", // Comment for translators "Don't translate the VERIFIER word!"), this); helpMessage2Label->setWordWrap(true); helpMessage2Label->setFont(helpFont); verifierIconLabel = new QLabel(this); verifierIconLabel->setPixmap(QIcon::fromTheme("dialog-password", QIcon(":/images/button-password.png")) .pixmap(64,64) .scaledToWidth(64, Qt::SmoothTransformation)); verifierLabel = new QLabel("" + tr("Verifier code:") + "", this); verifierLineEdit = new QLineEdit(this); QString verifierTooltip = tr("Enter or paste the verifier code provided " "by your Pump server here"); verifierLineEdit->setPlaceholderText(verifierTooltip); verifierLineEdit->setToolTip("" + verifierTooltip); authorizeApplicationButton = new QPushButton(QIcon::fromTheme("security-high"), tr("&Authorize Application"), this); connect(authorizeApplicationButton, SIGNAL(clicked()), this, SLOT(setVerifierCode())); // To notify invalid ID or empty verifier code // Cleared when typing in any of the two fields errorsLabel = new QLabel(this); errorsLabel->setOpenExternalLinks(true); connect(userIdLineEdit, SIGNAL(textChanged(QString)), errorsLabel, SLOT(clear())); connect(verifierLineEdit, SIGNAL(textChanged(QString)), errorsLabel, SLOT(clear())); QFont authorizationStatusFont; authorizationStatusFont.setPointSize(authorizationStatusFont.pointSize() + 2); authorizationStatusFont.setBold(true); authorizationStatusLabel = new QLabel(this); authorizationStatusLabel->setFont(authorizationStatusFont); saveButton = new QPushButton(QIcon::fromTheme("document-save", QIcon(":/images/button-save.png")), tr("&Save Details"), this); saveButton->setDisabled(true); // Disabled initially connect(saveButton, SIGNAL(clicked()), this, SLOT(saveDetails())); cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("&Cancel"), this); connect(cancelButton, SIGNAL(clicked()), this, SLOT(hide())); // Unlock button, visible when the account is already authorized, + info this->unlockExplanationLabel = new QLabel("" + tr("Your account is properly " "configured.") + "" "

              " + tr("Press Unlock if you wish " "to configure a different " "account.") + "
              ", this); unlockExplanationLabel->setAlignment(Qt::AlignHCenter); this->unlockButton = new QPushButton(QIcon::fromTheme("object-unlocked"), tr("&Unlock"), this); connect(unlockButton, SIGNAL(clicked()), this, SLOT(unlockDialog())); //////////////////////////////////////////////////////////////////// Layout this->idLayout = new QHBoxLayout(); idLayout->addWidget(userIDIconLabel); idLayout->addSpacing(4); idLayout->addWidget(userIdLabel); idLayout->addWidget(userIdLineEdit); idLayout->addWidget(getVerifierButton); this->verifierLayout = new QHBoxLayout(); verifierLayout->addWidget(verifierIconLabel); verifierLayout->addSpacing(4); verifierLayout->addWidget(verifierLabel); verifierLayout->addWidget(verifierLineEdit); verifierLayout->addWidget(authorizeApplicationButton); this->buttonsLayout = new QHBoxLayout(); buttonsLayout->setAlignment(Qt::AlignRight); buttonsLayout->addWidget(saveButton); buttonsLayout->addWidget(cancelButton); this->mainLayout = new QVBoxLayout(); mainLayout->addWidget(unlockExplanationLabel, 6); mainLayout->addWidget(unlockButton, 0, Qt::AlignHCenter); mainLayout->addWidget(helpMessage1Label, 6); mainLayout->addSpacing(8); mainLayout->addStretch(1); mainLayout->addLayout(idLayout, 2); mainLayout->addSpacing(16); mainLayout->addStretch(1); mainLayout->addWidget(separatorLine); mainLayout->addStretch(1); mainLayout->addSpacing(16); mainLayout->addWidget(helpMessage2Label, 2); mainLayout->addSpacing(8); mainLayout->addStretch(1); mainLayout->addLayout(verifierLayout, 2); mainLayout->addWidget(errorsLabel, 1, Qt::AlignCenter); mainLayout->addSpacing(2); mainLayout->addWidget(authorizationStatusLabel, 1, Qt::AlignCenter); mainLayout->addStretch(2); mainLayout->addSpacing(20); mainLayout->addLayout(buttonsLayout, 1); this->setLayout(mainLayout); QSettings settings; // Load saved User ID userIdLineEdit->setText(settings.value("userID").toString()); this->showAuthorizationStatus(settings.value("isApplicationAuthorized", false).toBool()); // Disable verifier input field and button initially // They will be used after requesting a token verifierLineEdit->setDisabled(true); authorizeApplicationButton->setDisabled(true); qDebug() << "Account dialog created"; } AccountDialog::~AccountDialog() { qDebug() << "Account dialog destroyed"; } void AccountDialog::setLockMode(bool locked) { if (locked) { this->helpMessage1Label->hide(); this->separatorLine->hide(); this->helpMessage2Label->hide(); this->unlockExplanationLabel->show(); this->unlockButton->show(); this->verifierLineEdit->setDisabled(true); this->authorizeApplicationButton->setDisabled(true); } else { this->helpMessage1Label->show(); this->separatorLine->show(); this->helpMessage2Label->show(); this->unlockExplanationLabel->hide(); this->unlockButton->hide(); } this->userIdLineEdit->setDisabled(locked); this->getVerifierButton->setDisabled(locked); } ////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////// SLOTS ////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void AccountDialog::askForToken() { QSettings settings; settings.setValue("isApplicationAuthorized", false); // Until steps are completed // Don't fail if there're spaces before or after the ID, they're only human ;) userIdLineEdit->setText(userIdLineEdit->text().trimmed()); // Match username@server.tld AND check for only 1 @ sign if (QRegExp("\\S+@\\S+\\.\\S+").exactMatch(this->userIdLineEdit->text()) && userIdLineEdit->text().count("@") == 1) { // Clear verifier field and re-enable it and the button this->verifierLineEdit->clear(); this->verifierLineEdit->setEnabled(true); this->authorizeApplicationButton->setEnabled(true); // Show message about the web browser that will be started this->errorsLabel->setText("[ " + tr("A web browser will start now, " "where you can get the verifier code") + " ]"); this->pController->setNewUserId(userIdLineEdit->text()); this->pController->getToken(); } else // userID does not match user@hostname.domain { this->errorsLabel->setText("[ " + tr("Your Pump address is invalid") + " ]"); qDebug() << "userID is invalid!"; } } void AccountDialog::setVerifierCode() { qDebug() << "AccountDialog::setVerifierCode()" << this->verifierLineEdit->text(); if (!this->verifierLineEdit->text().trimmed().isEmpty()) { this->pController->authorizeApplication(this->verifierLineEdit->text().trimmed()); this->saveButton->setEnabled(true); } else { this->errorsLabel->setText("[ " + tr("Verifier code is empty") + " ]"); } } void AccountDialog::showAuthorizationStatus(bool authorized) { if (authorized) { this->authorizationStatusLabel->setText(QString::fromUtf8("\342\234\224 ") // Check mark + tr("Dianara is authorized to " "access your data")); } else { this->authorizationStatusLabel->clear(); } this->setLockMode(this->pController->currentlyAuthorized()); } /* * Show the authorization URL in a label, * in case the browser doesn't open automatically * */ void AccountDialog::showAuthorizationURL(QUrl url) { QString message = tr("If the browser doesn't open automatically, " "copy this address manually") + ":
              " + url.toString() + ""; this->errorsLabel->setText(message); } /* * Save the new userID and inform other parts of the program about it * */ void AccountDialog::saveDetails() { QString newUserId = this->userIdLineEdit->text().trimmed(); if (newUserId.isEmpty() || !newUserId.contains("@")) { return; // If no user ID, or with no "@", ignore // FIXME } QSettings settings; settings.setValue("userID", newUserId); settings.sync(); emit userIDChanged(newUserId); this->errorsLabel->clear(); // Clear previous error messages, if any // If it's authorized, disable the Save Details button if (this->pController->currentlyAuthorized()) { this->saveButton->setDisabled(true); } this->hide(); // close() would end program if main window was hidden } void AccountDialog::unlockDialog() { this->setLockMode(false); } ////////////////////////////////////////////////////////////////////////////// /////////////////////////////////// PROTECTED //////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void AccountDialog::hideEvent(QHideEvent *event) { this->setLockMode(this->pController->currentlyAuthorized()); event->accept(); } dianara-v1.3.2/src/profileeditor.cpp000664 000764 000764 00000030061 12611533701 017037 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "profileeditor.h" ProfileEditor::ProfileEditor(PumpController *pumpController, QWidget *parent) : QWidget(parent) { this->setWindowTitle(tr("Profile Editor") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("user-properties", QIcon(":/images/no-avatar.png"))); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::ApplicationModal); this->setMinimumSize(500, 400); QSettings settings; this->resize(settings.value("ProfileEditor/profileWindowSize", QSize(580, 480)).toSize()); this->pController = pumpController; QFont infoFont; infoFont.setPointSize(infoFont.pointSize() - 1); webfingerLabel = new QLabel(this); webfingerLabel->setFont(infoFont); webfingerLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); webfingerLabel->setToolTip("" + tr("This is your Pump address")); const QString emailExplanation = tr("This is the e-mail address associated " "with your account, for things such as " "notifications and password recovery"); emailChanger = new EmailChanger(emailExplanation, this->pController, this); emailLabel = new QLabel(this); emailLabel->setFont(infoFont); emailLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); emailLabel->setToolTip("" // Make the tooltip HTML so it gets wordwrap + emailExplanation); changeEmailButton = new QPushButton(QIcon::fromTheme("view-pim-mail", QIcon(":/images/button-post.png")), tr("Change &E-mail..."), this); connect(changeEmailButton, SIGNAL(clicked()), emailChanger, SLOT(show())); avatarLabel = new QLabel(this); avatarLabel->setPixmap(QIcon::fromTheme("user-properties", QIcon(":/images/no-avatar.png")) .pixmap(96, 96)); changeAvatarButton = new QPushButton(QIcon::fromTheme("folder-image-people"), tr("Change &Avatar..."), this); connect(changeAvatarButton, SIGNAL(clicked()), this, SLOT(findAvatarFile())); this->avatarChanged = false; fullNameLineEdit = new QLineEdit(this); fullNameLineEdit->setToolTip("" + tr("This is your visible name")); connect(fullNameLineEdit, SIGNAL(textEdited(QString)), this, SLOT(enableSaveButton())); hometownLineEdit = new QLineEdit(this); connect(hometownLineEdit, SIGNAL(textEdited(QString)), this, SLOT(enableSaveButton())); bioTextEdit = new QTextEdit(this); bioTextEdit->setTabChangesFocus(true); bioTextEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); connect(bioTextEdit, SIGNAL(textChanged()), this, SLOT(enableSaveButton())); saveButton = new QPushButton(QIcon::fromTheme("document-save", QIcon(":/images/button-save.png")), tr("&Save Profile"), this); connect(saveButton, SIGNAL(clicked()), this, SLOT(saveProfile())); cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("&Cancel"), this); connect(cancelButton, SIGNAL(clicked()), this, SLOT(close())); // ESC to cancel, too cancelAction = new QAction(this); cancelAction->setShortcut(QKeySequence(Qt::Key_Escape)); connect(cancelAction, SIGNAL(triggered()), this, SLOT(close())); this->addAction(cancelAction); // Layout this->emailLayout = new QHBoxLayout(); emailLayout->addWidget(emailLabel); emailLayout->addStretch(1); emailLayout->addWidget(changeEmailButton); this->avatarLayout = new QHBoxLayout(); avatarLayout->addWidget(avatarLabel); avatarLayout->addStretch(1); avatarLayout->addWidget(changeAvatarButton); this->topLayout = new QFormLayout(); topLayout->addRow(tr("Webfinger ID"), webfingerLabel); topLayout->addRow(tr("E-mail"), emailLayout); topLayout->addRow(tr("Avatar"), avatarLayout); topLayout->addRow(tr("Full &Name"), fullNameLineEdit); topLayout->addRow(tr("&Hometown"), hometownLineEdit); topLayout->addRow(tr("&Bio"), bioTextEdit); // FIXME: the FormLayout doesn't let the bioTextField row grow taller than its sizeHint() this->bottomLayout = new QHBoxLayout(); bottomLayout->setAlignment(Qt::AlignRight | Qt::AlignBottom); bottomLayout->addWidget(saveButton); bottomLayout->addWidget(cancelButton); this->mainLayout = new QVBoxLayout(); mainLayout->addLayout(topLayout); mainLayout->addLayout(bottomLayout); this->setLayout(mainLayout); // Disable some stuff until profile is received this->toggleWidgetsEnabled(false); this->saveButton->setEnabled(false); qDebug() << "ProfileEditor created"; } ProfileEditor::~ProfileEditor() { qDebug() << "ProfileEditor destroyed"; } /* * Fill the fields from received info * */ void ProfileEditor::setProfileData(QString avatarUrl, QString fullName, QString hometown, QString bio, QString eMail) { this->webfingerLabel->setText(pController->currentUserId()); if (eMail.isEmpty()) { this->emailLabel->setText("<" + tr("Not set", "In reference to the e-mail " "not being set for the account") + ">"); } else { this->emailLabel->setText(eMail); this->emailChanger->setCurrentEmail(eMail); } this->currentAvatarURL = avatarUrl; QString avatarFilename = MiscHelpers::getCachedAvatarFilename(this->currentAvatarURL); if (QFile::exists(avatarFilename)) { this->avatarLabel->setPixmap(QPixmap(avatarFilename).scaled(96, 96, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { // FIXME: Should fetch the avatar } this->fullNameLineEdit->setText(fullName); this->hometownLineEdit->setText(hometown); this->bioTextEdit->setText(bio); // Enable all widgets, since the profile is valid now this->toggleWidgetsEnabled(true); this->saveButton->setDisabled(true); // But disable Save button until necessary } /* * Enable or disable some widgets depending on whether the profile has been received * */ void ProfileEditor::toggleWidgetsEnabled(bool state) { this->changeEmailButton->setEnabled(state); this->changeAvatarButton->setEnabled(state); this->fullNameLineEdit->setEnabled(state); this->hometownLineEdit->setEnabled(state); this->bioTextEdit->setEnabled(state); } /****************************************************************************/ /******************************** SLOTS *************************************/ /****************************************************************************/ void ProfileEditor::findAvatarFile() { newAvatarFilename = QFileDialog::getOpenFileName(this, tr("Select avatar image"), QDir::homePath(), tr("Image files") + " (*.png *.jpg *.jpeg *.gif);;" + tr("All files") + " (*)"); if (!newAvatarFilename.isEmpty()) { qDebug() << "Selected" << newAvatarFilename << "as new avatar for upload"; // FIXME: in the future, check file size and image size // and scale the pixmap to something sane before uploading. QPixmap avatarPixmap = QPixmap(newAvatarFilename); this->newAvatarContentType = MiscHelpers::getFileMimeType(newAvatarFilename); if (!avatarPixmap.isNull() && !newAvatarContentType.isEmpty()) { this->avatarLabel->setPixmap(QPixmap(newAvatarFilename) .scaled(96, 96, Qt::KeepAspectRatio, Qt::SmoothTransformation)); this->avatarChanged = true; this->enableSaveButton(); // Enable, since something has been changed } else { QMessageBox::warning(this, tr("Invalid image"), tr("The selected image is not valid.")); qDebug() << "Invalid avatar file selected"; } } } void ProfileEditor::saveProfile() { if (avatarChanged) { connect(pController, SIGNAL(avatarUploaded(QString)), this, SLOT(sendProfileData(QString))); this->pController->uploadFile(this->newAvatarFilename, this->newAvatarContentType, PumpController::UploadAvatarRequest); } else { this->sendProfileData(); // without a new image ID } } void ProfileEditor::sendProfileData(QString newImageUrl) { if (avatarChanged) { disconnect(pController, SIGNAL(avatarUploaded(QString)), this, SLOT(sendProfileData(QString))); this->avatarChanged = false; // For next time the dialog is shown } QString newFullName = this->fullNameLineEdit->text().trimmed(); if (newFullName.isEmpty()) { // To avoid having empty names, use the username part from the ID newFullName = this->pController->currentUsername(); } this->pController->updateUserProfile(newImageUrl, newFullName, this->hometownLineEdit->text().trimmed(), this->bioTextEdit->toPlainText().trimmed()); this->close(); } /* * Enable the 'Save' button when something actually changes * */ void ProfileEditor::enableSaveButton() { this->saveButton->setEnabled(true); } /****************************************************************************/ /****************************** PROTECTED ***********************************/ /****************************************************************************/ void ProfileEditor::closeEvent(QCloseEvent *event) { this->saveButton->setDisabled(true); // Disabled for next time QSettings settings; if (settings.isWritable()) { settings.setValue("ProfileEditor/profileWindowSize", this->size()); } this->hide(); event->ignore(); } dianara-v1.3.2/src/asobject.h000664 000764 000764 00000007566 12514736674 015465 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef ASOBJECT_H #define ASOBJECT_H #include #include #include #include "asperson.h" #include "timestamp.h" class ASObject : public QObject { Q_OBJECT public: explicit ASObject(QVariantMap objectMap, QObject *parent = 0); ~ASObject(); void updateAuthorFromPerson(ASPerson *person); ASPerson *author(); QString getId(); QString getType(); static QString getTranslatedType(QString typeString); QString getUrl(); QString getCreatedAt(); QString getUpdatedAt(); QString getLocationName(); QString getLocationFormatted(); QString getLocationCountry(); QString getLocationTooltip(); QString getDeletedTime(); QString getDeletedOnString(); static QString makeDeletedOnString(QString deletionTime); QString isLiked(); QString getTitle(); QString getSummary(); QString getContent(); QString getImageUrl(); QString getAudioUrl(); QString getVideoUrl(); QString getFileUrl(); QString getMimeType(); QString getAttachmentPureUrl(); int getMemberCount(); QString getMemberUrl(); QString getLikesCount(); QString getCommentsCount(); QString getSharesCount(); bool hasProxiedUrls(); QVariantList getLastLikesList(); QVariantList getLastCommentsList(); QVariantList getLastSharesList(); QString getLikesUrl(); QString getCommentsUrl(); QString getSharesUrl(); QVariantMap getOriginalObject(); QVariantMap getInReplyTo(); QString getInReplyToId(); // This one to deprecate static QString personStringFromList(QVariantList variantList, int count=-1); static QVariantMap simplePersonMapFromList(QVariantList variantList); static void addOnePersonToSimpleMap(QString personId, QString personName, QString personUrl, QVariantMap *personMap); static QString personStringFromSimpleMap(QVariantMap personMap, int totalCount); static bool canDisplayObject(QString objectType); signals: public slots: private: ASPerson *asAuthor; QString id; QString type; QString url; QString createdAt; QString updatedAt; QString locationName; QString locationFormatted; QString locationCountry; QString deleted; QString liked; QString title; QString summary; QString content; QString imageUrl; QString audioUrl; QString videoUrl; QString fileUrl; QString mimeType; QString attachmentPureUrl; // To have filename with extension when using a proxyURL int memberCount; QString memberUrl; QString likesCount; QString commentsCount; QString sharesCount; QVariantList lastLikesList; QVariantList lastCommentsList; QVariantList lastSharesList; QString likesUrl; QString commentsUrl; QString sharesUrl; bool proxiedUrls; QVariantMap originalObjectMap; QVariantMap inReplyToMap; }; #endif // ASOBJECT_H dianara-v1.3.2/src/accountdialog.h000664 000764 000764 00000004537 12541125400 016455 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef ACCOUNT_H #define ACCOUNT_H #include #include #include #include #include #include #include #include #include #include #include #include "pumpcontroller.h" class AccountDialog : public QWidget { Q_OBJECT public: AccountDialog(PumpController *pumpController, QWidget *parent = 0); ~AccountDialog(); void setLockMode(bool locked); signals: void userIDChanged(QString newUserID); public slots: void askForToken(); void setVerifierCode(); void showAuthorizationStatus(bool authorized); void showAuthorizationURL(QUrl url); void saveDetails(); void unlockDialog(); protected: virtual void hideEvent(QHideEvent *event); private: QVBoxLayout *mainLayout; QLabel *helpMessage1Label; QHBoxLayout *idLayout; QLabel *userIDIconLabel; QLabel *userIdLabel; QLineEdit *userIdLineEdit; QPushButton *getVerifierButton; QFrame *separatorLine; QLabel *helpMessage2Label; QHBoxLayout *verifierLayout; QLabel *verifierIconLabel; QLabel *verifierLabel; QLineEdit *verifierLineEdit; QPushButton *authorizeApplicationButton; QLabel *errorsLabel; QLabel *authorizationStatusLabel; QLabel *unlockExplanationLabel; QPushButton *unlockButton; QHBoxLayout *buttonsLayout; QPushButton *saveButton; QPushButton *cancelButton; PumpController *pController; }; #endif // ACCOUNT_H dianara-v1.3.2/src/contactmanager.cpp000644 000764 000764 00000034415 12605277726 017202 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "contactmanager.h" ContactManager::ContactManager(PumpController *pumpController, GlobalObject *globalObject, QWidget *parent) : QWidget(parent) { this->pController = pumpController; this->globalObj = globalObject; // After receiving a contact list, update it connect(pController, SIGNAL(contactListReceived(QString,QVariantList,int)), this, SLOT(setContactListsContents(QString,QVariantList,int))); // After receiving the list of lists, update it connect(pController, SIGNAL(listsListReceived(QVariantList)), this, SLOT(setListsListContents(QVariantList))); mainLayout = new QVBoxLayout(); mainLayout->setAlignment(Qt::AlignTop); QString webfingerHelpMessage = tr("username@server.org or " "https://server.org/username"); topLayout = new QHBoxLayout(); enterAddressLabel = new QLabel(tr("&Enter address to follow:"), this); enterAddressLabel->setToolTip("" + webfingerHelpMessage); // HTML tags for wordwrap // Ensure it will get focus first, before addressLineEdit enterAddressLabel->setFocusPolicy(Qt::StrongFocus); addressLineEdit = new QLineEdit(this); addressLineEdit->setPlaceholderText(webfingerHelpMessage); addressLineEdit->setToolTip("" + webfingerHelpMessage); // HTML tags to get wordwrap connect(addressLineEdit, SIGNAL(textChanged(QString)), this, SLOT(toggleFollowButton(QString))); connect(addressLineEdit, SIGNAL(returnPressed()), this, SLOT(followContact())); enterAddressLabel->setBuddy(addressLineEdit); followButton = new QPushButton(QIcon::fromTheme("list-add-user", QIcon(":/images/list-add.png")), tr("&Follow"), this); followButton->setDisabled(true); // Disabled until an address is typed connect(followButton, SIGNAL(clicked()), this, SLOT(followContact())); topLayout->addWidget(enterAddressLabel); topLayout->addWidget(addressLineEdit); topLayout->addWidget(followButton); mainLayout->addLayout(topLayout); mainLayout->addSpacing(4); // Widgets for list of 'following' and 'followers' this->followingWidget = new ContactList(this->pController, this->globalObj, "following", this); connect(pController, SIGNAL(contactFollowed(ASPerson*)), followingWidget, SLOT(addSingleContact(ASPerson*))); connect(pController, SIGNAL(contactUnfollowed(ASPerson*)), followingWidget, SLOT(removeSingleContact(ASPerson*))); connect(followingWidget, SIGNAL(contactCountChanged(int)), this, SLOT(changeFollowingCount(int))); this->followersWidget = new ContactList(this->pController, this->globalObj, "followers", this); // Widget for the list of 'person lists' this->listsManager = new ListsManager(this->pController, this); listsScrollArea = new QScrollArea(this); listsScrollArea->setWidget(this->listsManager); listsScrollArea->setWidgetResizable(true); listsScrollArea->setFrameStyle(QFrame::NoFrame); // Widget for the list of site users; users from your own server this->siteUsersList = new SiteUsersList(this->pController, this->globalObj, this); // Options menu optionsMenu = new QMenu("*options-menu*", this); optionsMenu->addAction(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), tr("Reload Followers"), this, SLOT(refreshFollowers())); optionsMenu->addAction(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), tr("Reload Following"), this, SLOT(refreshFollowing())); optionsMenu->addSeparator(); optionsMenu->addAction(QIcon::fromTheme("document-export", QIcon(":/images/button-download.png")), tr("Export Followers"), this, SLOT(exportFollowers())); optionsMenu->addAction(QIcon::fromTheme("document-export", QIcon(":/images/button-download.png")), tr("Export Following"), this, SLOT(exportFollowing())); optionsMenu->addSeparator(); optionsMenu->addAction(QIcon::fromTheme("view-refresh", QIcon(":/images/menu-refresh.png")), tr("Reload Lists"), this, SLOT(refreshPersonLists())); optionsButton = new QPushButton(QIcon::fromTheme("configure", QIcon(":/images/button-configure.png")), "", this); optionsButton->setMenu(optionsMenu); this->tabWidget = new QTabWidget(this); tabWidget->addTab(followersWidget, QIcon::fromTheme("meeting-observer", QIcon(":/images/no-avatar.png")), "*followers*"); tabWidget->addTab(followingWidget, QIcon::fromTheme("meeting-participant", QIcon(":/images/no-avatar.png")), "*following*"); tabWidget->addTab(listsScrollArea, QIcon::fromTheme("preferences-contact-list", QIcon(":/images/button-edit.png")), "*lists*"); tabWidget->addTab(siteUsersList, QIcon::fromTheme("system-users", QIcon(":/images/no-avatar.png")), tr("&Neighbors")); tabWidget->setCornerWidget(this->optionsButton); this->followersCount = 0; this->followingCount = 0; this->listsCount = 0; this->setTabLabels(); mainLayout->addWidget(tabWidget); this->setLayout(mainLayout); qDebug() << "Contact manager created"; } ContactManager::~ContactManager() { qDebug() << "Contact manager destroyed"; } void ContactManager::setTabLabels() { this->tabWidget->setTabText(0, tr("Follo&wers") + QString(" (%1)") .arg(QLocale::system() .toString(this->followersCount))); this->tabWidget->setTabText(1, tr("Followin&g") + QString(" (%1)") .arg(QLocale::system() .toString(this->followingCount))); this->tabWidget->setTabText(2, tr("&Lists") + QString(" (%1)").arg(this->listsCount)); // Not worth localizing } /* * Write the list of contacts (following or followers) * to a file selected by the user * */ void ContactManager::exportContactsToFile(QString listType) { QString dialogTitle = listType == "following" ? tr("Export list of 'following' to a file") : tr("Export list of 'followers' to a file"); QString suggestedFilename = "dianara-" + pController->currentUsername() + "-" + listType; QString filename = QFileDialog::getSaveFileName(this, dialogTitle, QDir::homePath() + "/" + suggestedFilename, "").trimmed(); if (filename.isEmpty()) // If dialog was cancelled, do nothing { return; } qDebug() << "Exporting to:" << filename; QFile exportFile(filename); exportFile.open(QIODevice::WriteOnly); if (listType == "following") { exportFile.write(this->followingWidget->getContactsStringForExport().toLocal8Bit()); } else // "followers" { exportFile.write(this->followersWidget->getContactsStringForExport().toLocal8Bit()); } exportFile.close(); } /*****************************************************************************/ /*********************************** SLOTS ***********************************/ /*****************************************************************************/ void ContactManager::setContactListsContents(QString listType, QVariantList contactList, int totalReceivedCount) { qDebug() << "ContactManager; Setting contact list contents"; if (listType == "following") { if (totalReceivedCount <= 200) // Only for the first batch { this->followingWidget->clearListContents(); followingCount = 0; } followingWidget->setListContents(contactList); if (totalReceivedCount < pController->currentFollowingCount()) { pController->getContactList(listType, totalReceivedCount); } this->followingCount += contactList.size(); } else { if (totalReceivedCount <= 200) { this->followersWidget->clearListContents(); followersCount = 0; } followersWidget->setListContents(contactList); if (totalReceivedCount < pController->currentFollowersCount()) { pController->getContactList(listType, totalReceivedCount); } this->followersCount += contactList.size(); } // Update tab labels with number of following or followers, which were updated before this->setTabLabels(); } /* * Fill the list of lists * */ void ContactManager::setListsListContents(QVariantList listsList) { this->listsCount = listsList.count(); // Update tab labels with number of following or followers, which were updated before this->setTabLabels(); this->listsManager->setListsList(listsList); } void ContactManager::changeFollowingCount(int difference) { this->followingCount += difference; // FIXME: pumpController should be notified this->setTabLabels(); // Set "Following" tab as active, only when the contact manager is not visible // (following someone from an AvatarButton menu) if (!this->isVisible()) { this->tabWidget->setCurrentIndex(1); } } /* * Ask for the updated list of Following * */ void ContactManager::refreshFollowing() { qDebug() << "Refreshing list of Following..."; this->pController->getContactList("following"); } /* * Ask for the updated list of followers * */ void ContactManager::refreshFollowers() { qDebug() << "Refreshing list of Followers..."; this->pController->getContactList("followers"); } /* * Export list of "Following" to a text file * */ void ContactManager::exportFollowing() { qDebug() << "Exporting Following..."; exportContactsToFile("following"); } /* * Export list of "Followers" to a text file * */ void ContactManager::exportFollowers() { qDebug() << "Exporting Followers..."; exportContactsToFile("followers"); } void ContactManager::refreshPersonLists() { qDebug() << "Refreshing list of person lists..."; this->pController->getListsList(); } /* * Enable or disable Follow button * */ void ContactManager::toggleFollowButton(QString currentAddress) { if (currentAddress.isEmpty()) { this->followButton->setDisabled(true); } else { this->followButton->setEnabled(true); } } /* * Add the address entered by the user to the /following list. * * This supports adding webfinger addresses in the form * user@hostname or https://host/username. * */ void ContactManager::followContact() { QString address = this->addressLineEdit->text().trimmed(); bool validAddress = false; qDebug() << "ContactManager::followContact(); Address entered:" << address; // First, if the address is in URL form, convert it if (address.startsWith("https://") || address.startsWith("http://")) { address.remove("https://"); address.remove("http://"); if (address.contains("/")) // Very basic sanity check { QStringList addressParts = address.split("/"); address = addressParts.at(1) + "@" + addressParts.at(0); // .at(2) could also have stuff, so don't use .first() and .last() } } // Then, check that the address actually matches something@somewhere.tld if (address.contains(QRegExp(".+@.+\\..+"))) { validAddress = true; } else { qDebug() << "Invalid webfinger address!"; this->addressLineEdit->setFocus(); } if (validAddress) { qDebug() << "About to follow this address:" << address; this->pController->followContact(address); this->addressLineEdit->clear(); // Activate 'Following' tab this->tabWidget->setFocus(); this->tabWidget->setCurrentIndex(1); } } dianara-v1.3.2/src/audienceselector.h000664 000764 000764 00000005077 12461234363 017171 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef AUDIENCESELECTOR_H #define AUDIENCESELECTOR_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pumpcontroller.h" #include "peoplewidget.h" class AudienceSelector : public QFrame { Q_OBJECT public: explicit AudienceSelector(PumpController *pumpController, QString selectorType, QWidget *parent = 0); ~AudienceSelector(); void resetLists(); void deletePrevious(); void saveSelected(); void restoreSelected(); signals: void audienceSelected(QString selectorType, QStringList contactsList, QStringList urlsList); public slots: void copyToSelected(QIcon contactIcon, QString contactString, QString contactUrl); void setAudience(); protected: virtual void closeEvent(QCloseEvent *event); virtual void hideEvent(QHideEvent *event); private: QString selectorType; QHBoxLayout *mainLayout; QVBoxLayout *allGroupboxLayout; QGroupBox *allContactsGroupbox; PeopleWidget *peopleWidget; QVBoxLayout *selectedGroupboxLayout; QGroupBox *selectedListGroupbox; QLabel *explanationLabel; QListWidget *selectedListWidget; QList previousItems; QPushButton *clearSelectedListButton; QHBoxLayout *buttonsLayout; QPushButton *doneButton; QPushButton *cancelButton; QAction *doneAction; QAction *cancelAction; }; #endif // AUDIENCESELECTOR_H dianara-v1.3.2/src/audienceselector.cpp000664 000764 000764 00000023042 12612152356 017513 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "audienceselector.h" AudienceSelector::AudienceSelector(PumpController *pumpController, QString selectorType, QWidget *parent) : QFrame(parent) { this->selectorType = selectorType; QString titlePart; if (this->selectorType == "to") { titlePart = tr("'To' List"); } else { titlePart = tr("'Cc' List"); } this->setWindowTitle(titlePart + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("system-users", QIcon(":/images/no-avatar.png"))); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::WindowModal); this->setMinimumSize(440, 340); QSettings settings; QSize savedWindowsize = settings.value("AudienceSelector/" "audienceWindowSize").toSize(); if (savedWindowsize.isValid()) { this->resize(savedWindowsize); } // Left side, all contacts, with filter this->peopleWidget = new PeopleWidget(tr("&Add to Selected") + " >>", PeopleWidget::EmbeddedWidget, pumpController, this); connect(peopleWidget, SIGNAL(contactSelected(QIcon,QString,QString)), this, SLOT(copyToSelected(QIcon,QString,QString))); connect(peopleWidget, SIGNAL(addButtonPressed(QIcon,QString,QString)), this, SLOT(copyToSelected(QIcon,QString,QString))); this->allGroupboxLayout = new QVBoxLayout(); allGroupboxLayout->addWidget(peopleWidget); allContactsGroupbox = new QGroupBox(tr("All Contacts"), this); allContactsGroupbox->setLayout(allGroupboxLayout); // Right side, selected contacts explanationLabel = new QLabel(tr("Select people from the list on the left.\n" "You can drag them with the mouse, click or " "double-click on them, or select them and " "use the button below.", "ON THE LEFT should change to ON THE " "RIGHT in RTL languages"), this); explanationLabel->setWordWrap(true); selectedListWidget = new QListWidget(this); selectedListWidget->setDragDropMode(QListView::DragDrop); selectedListWidget->setDefaultDropAction(Qt::MoveAction); selectedListWidget->setSelectionMode(QListView::ExtendedSelection); selectedListWidget->setIconSize(QSize(48, 48)); this->clearSelectedListButton = new QPushButton(QIcon::fromTheme("edit-clear-list", QIcon(":/images/button-delete.png")), tr("Clear &List"), this); connect(clearSelectedListButton, SIGNAL(clicked()), selectedListWidget, SLOT(clear())); doneButton = new QPushButton(QIcon::fromTheme("dialog-ok", QIcon(":/images/button-save.png")), tr("&Done"), this); connect(doneButton, SIGNAL(clicked()), this, SLOT(setAudience())); cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("&Cancel"), this); connect(cancelButton, SIGNAL(clicked()), this, SLOT(close())); buttonsLayout = new QHBoxLayout(); buttonsLayout->setAlignment(Qt::AlignRight); buttonsLayout->addWidget(doneButton); buttonsLayout->addWidget(cancelButton); selectedGroupboxLayout = new QVBoxLayout(); selectedGroupboxLayout->addWidget(explanationLabel); selectedGroupboxLayout->addSpacing(12); selectedGroupboxLayout->addWidget(selectedListWidget); selectedGroupboxLayout->addWidget(clearSelectedListButton); selectedGroupboxLayout->addSpacing(8); selectedGroupboxLayout->addLayout(buttonsLayout); this->selectedListGroupbox = new QGroupBox(tr("Selected People"), this); selectedListGroupbox->setLayout(selectedGroupboxLayout); this->mainLayout = new QHBoxLayout(); mainLayout->addWidget(allContactsGroupbox, 3); mainLayout->addWidget(selectedListGroupbox, 4); this->setLayout(mainLayout); // Ctrl+Enter is the same as the "Done" button doneAction = new QAction(this); QList doneShortcuts; doneShortcuts << QKeySequence("Ctrl+Return") << QKeySequence("Ctrl+Enter"); doneAction->setShortcuts(doneShortcuts); connect(doneAction, SIGNAL(triggered()), this, SLOT(setAudience())); this->addAction(doneAction); // ESC is the same as the "Cancel" button cancelAction = new QAction(this); cancelAction->setShortcut(Qt::Key_Escape); connect(cancelAction, SIGNAL(triggered()), this, SLOT(close())); this->addAction(cancelAction); qDebug() << "AudienceSelector created" << titlePart; } AudienceSelector::~AudienceSelector() { qDebug() << "AudienceSelector destroyed"; } /* * Reset lists and widgets to default status * */ void AudienceSelector::resetLists() { this->peopleWidget->resetWidget(); this->selectedListWidget->clear(); restoreSelected(); } void AudienceSelector::deletePrevious() { foreach (QListWidgetItem *item, previousItems) { delete item; } previousItems.clear(); } void AudienceSelector::saveSelected() { qDebug() << "AudienceSelector::saveSelected()"; // Clear and delete all first this->deletePrevious(); int totalItems = this->selectedListWidget->count(); for (int counter = 0; counter < totalItems; ++counter) { this->previousItems.append(selectedListWidget->item(counter)->clone()); } } void AudienceSelector::restoreSelected() { qDebug() << "AudienceSelector::restoreSelected()"; foreach (QListWidgetItem *item, this->previousItems) { this->selectedListWidget->addItem(item->clone()); } } ////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// SLOTS ////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /* * Copy a contact to the list of Selected * * The contact string comes in a SIGNAL from PeopleWidget * */ void AudienceSelector::copyToSelected(QIcon contactIcon, QString contactString, QString contactUrl) { if (!contactString.isEmpty()) { int itemExists = selectedListWidget->findItems(contactString, Qt::MatchExactly).size(); if (itemExists == 0) { QListWidgetItem *item = new QListWidgetItem(contactIcon, contactString); item->setData(Qt::UserRole + 1, contactUrl); this->selectedListWidget->addItem(item); } else { qDebug() << "AudienceSelector::copyToSelected() " "ignoring already added recipient"; } } } /* * The "Done" button: emit signal with list of selected people * */ void AudienceSelector::setAudience() { int addressCount = this->selectedListWidget->count(); QStringList addressList; QStringList urlList; for (int counter=0; counter < addressCount; ++counter) { addressList.append(selectedListWidget->item(counter)->text()); urlList.append(selectedListWidget->item(counter)->data(Qt::UserRole + 1) .toString()); } emit audienceSelected(selectorType, addressList, urlList); saveSelected(); // To restore the list later, if the dialog is shown again this->hide(); // Don't close(), because that resets the lists =) } ////////////////////////////////////////////////////////////////////////////// /////////////////////////////// PROTECTED //////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void AudienceSelector::closeEvent(QCloseEvent *event) { this->resetLists(); this->hide(); event->accept(); } void AudienceSelector::hideEvent(QHideEvent *event) { QSettings settings; if (settings.isWritable()) { settings.setValue("AudienceSelector/audienceWindowSize", this->size()); } event->accept(); } dianara-v1.3.2/src/minorfeeditem.h000664 000764 000764 00000005043 12475430351 016473 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef MINORFEEDITEM_H #define MINORFEEDITEM_H #include #include #include #include #include #include #include #include #include "pumpcontroller.h" #include "globalobject.h" #include "timestamp.h" #include "asactivity.h" #include "post.h" #include "avatarbutton.h" class MinorFeedItem : public QFrame { Q_OBJECT public: explicit MinorFeedItem(ASActivity *activity, bool highlightedByFilter, PumpController *pumpController, GlobalObject *globalObject, QWidget *parent = 0); ~MinorFeedItem(); void setItemAsNew(bool isNew, bool informFeed); bool isNew(); void setFuzzyTimeStamp(); void syncAvatarFollowState(); int getItemHighlightType(); QString getActivityId(); signals: void itemRead(bool wasHighlighted); public slots: void openOriginalPost(); void showUrlInfo(QString url); protected: virtual void mousePressEvent(QMouseEvent *event); virtual void leaveEvent(QEvent *event); private: PumpController *pController; GlobalObject *globalObj; QHBoxLayout *mainLayout; QVBoxLayout *leftLayout; QVBoxLayout *rightLayout; QHBoxLayout *rightLowerLayout; AvatarButton *avatarButton; QLabel *timestampLabel; QLabel *activityDescriptionLabel; QPushButton *openButton; QVariantMap originalObjectMap; QVariantMap inReplyToMap; AvatarButton *targetAvatarButton; bool haveTargetAvatar; bool itemIsNew; int itemHighlightType; QString createdAtTimestamp; QString activityId; }; #endif // MINORFEEDITEM_H dianara-v1.3.2/src/minorfeeditem.cpp000664 000764 000764 00000040617 12557242441 017036 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "minorfeeditem.h" MinorFeedItem::MinorFeedItem(ASActivity *activity, bool highlightedByFilter, PumpController *pumpController, GlobalObject *globalObject, QWidget *parent) : QFrame(parent) { this->pController = pumpController; this->globalObj = globalObject; this->itemIsNew = false; this->haveTargetAvatar = false; // This sizePolicy prevents cut messages, and the huge space at the end // of the feed, after clicking "Older Activities" several times this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); activity->setParent(this); // reparent the passed activity // Store activity ID so MinorFeed can reconstruct "next" url's this->activityId = activity->getId(); QFont mainFont; mainFont.setPointSize(mainFont.pointSize() - 2); QString authorId = activity->author()->getId(); avatarButton = new AvatarButton(activity->author(), this->pController, this->globalObj, QSize(32,32), this); createdAtTimestamp = activity->getCreatedAt(); QString timestampTooltip = "" + Timestamp::localTimeDate(createdAtTimestamp) + ""; QString generator = activity->getGenerator(); if (!generator.isEmpty()) { timestampTooltip.append("
              " + tr("Using %1", "Application used to generate this activity") .arg(generator)); } QString toField = activity->getToString(); QString ccField = activity->getCCString(); if (!toField.isEmpty() || !ccField.isEmpty()) { timestampTooltip.append("
              "); // Extra separation before To or Cc if (!toField.isEmpty()) { timestampTooltip.append("
              " + tr("To: %1", "1=people to whom this activity was sent") .arg(toField)); } if (!ccField.isEmpty()) { timestampTooltip.append("
              " + tr("Cc: %1", "1=people to whom this activity was sent as CC") .arg(ccField)); } } timestampLabel = new QLabel(this); mainFont.setBold(true); timestampLabel->setFont(mainFont); timestampLabel->setWordWrap(true); timestampLabel->setToolTip(timestampTooltip.trimmed()); timestampLabel->setAlignment(Qt::AlignCenter); timestampLabel->setAutoFillBackground(true); timestampLabel->setForegroundRole(QPalette::Text); timestampLabel->setBackgroundRole(QPalette::Base); timestampLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); this->setFuzzyTimeStamp(); // The description itself, like "JaneDoe updated a note", activityDescriptionLabel = new QLabel(this); // To be filled later QFont descriptionFont; descriptionFont.fromString(globalObj->getMinorFeedFont()); activityDescriptionLabel->setWordWrap(true); activityDescriptionLabel->setOpenExternalLinks(true); activityDescriptionLabel->setAlignment(Qt::AlignTop); activityDescriptionLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); activityDescriptionLabel->setFont(descriptionFont); connect(activityDescriptionLabel, SIGNAL(linkHovered(QString)), this, SLOT(showUrlInfo(QString))); // Tooltip contents QString activityTooltip = activity->generateTooltip(); if (!activityTooltip.isEmpty()) { // Set the activity info as tooltip for description label this->activityDescriptionLabel->setToolTip(activityTooltip); // Set a different mouse cursor for the description label, as a hint to wait for tooltip info this->activityDescriptionLabel->setCursor(Qt::WhatsThisCursor); } originalObjectMap = activity->object()->getOriginalObject(); inReplyToMap = activity->object()->getInReplyTo(); QString inReplyToAuthorId = ASPerson::cleanupId(inReplyToMap.value("author").toMap() .value("id").toString()); // Highlight this item if it's about the user, with different colors itemHighlightType = -1; // False QString highlightItemColor; // We are in the recipient list of the activity if (activity->getRecipientsIdList().contains("acct:" + pController->currentUserId())) { itemHighlightType = 0; highlightItemColor = this->globalObj->getColor(itemHighlightType); } // Activity is in reply to something made by us if (inReplyToAuthorId == pController->currentUserId()) { itemHighlightType = 1; highlightItemColor = this->globalObj->getColor(itemHighlightType); } // We are the object; someone added us, etc. if (activity->object()->getId() == pController->currentUserId()) { itemHighlightType = 2; highlightItemColor = this->globalObj->getColor(itemHighlightType); } // The object is ours; someone favorited our note, or something if (activity->object()->author()->getId() == pController->currentUserId()) { itemHighlightType = 3; highlightItemColor = this->globalObj->getColor(itemHighlightType); } //// Special case: highlighting the item because there's a filter rule for it if (highlightedByFilter && itemHighlightType == -1) // Only if there's no other reason for HL { itemHighlightType = 4; highlightItemColor = this->globalObj->getColor(itemHighlightType); } if (itemHighlightType != -1) { // Unless the user ID is also empty! if (!pController->currentUserId().isEmpty()) { if (QColor::isValidColor(highlightItemColor)) // Valid color { // CSS for horizontal gradient from configured color to transparent QString css = QString("MinorFeedItem " "{ background-color: " "qlineargradient(spread:pad, " "x1:0, y1:0, x2:1, y2:0, " "stop:0 %1, stop:1 rgba(0, 0, 0, 0)); " "}") .arg(highlightItemColor); this->setStyleSheet(css); } else // If there's no valid color, highlight with a border { this->setFrameStyle(QFrame::Panel); } } } // Set the activity description with optional snippet QString activityDescription = activity->getContent(); // Add a snippet if configured to do so if (this->globalObj->getMinorFeedSnippetsType() != 3) // 3=Never { if (this->globalObj->getMinorFeedSnippetsType() == 2 // 2=Always || this->itemHighlightType != -1) // 0/1/2 and it's highlighted { if (this->globalObj->getMinorFeedSnippetsType() > 0 // Always or any highlighted || authorId != pController->currentUserId()) // or highlighted but not ours (0) { // FIXME: fix this spaghetti mess!! // TODO: option to not show when the object of the activity is ours // i.e. "JohnDoe liked a note", our note QString snippet = activity->generateSnippet(globalObj->getSnippetsCharLimit()); if (!snippet.isEmpty()) { activityDescription.append(" 
              " + snippet); } } } } this->activityDescriptionLabel->setText(activityDescription); //////////////////////////////////////////////////////// Layout leftLayout = new QVBoxLayout(); leftLayout->setAlignment(Qt::AlignTop); leftLayout->setContentsMargins(1, 0, 1, 0); leftLayout->setSpacing(1); leftLayout->addWidget(avatarButton, 0, Qt::AlignTop | Qt::AlignLeft); leftLayout->addStretch(0); // Original post available (inReplyTo) or object available (note, image...) if (!inReplyToMap.isEmpty() || (ASObject::canDisplayObject(activity->object()->getType()) && activity->object()->getDeletedTime().isEmpty()) ) { this->openButton = new QPushButton("+"); openButton->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum); openButton->setToolTip("" + tr("Open referenced post")); connect(openButton, SIGNAL(clicked()), this, SLOT(openOriginalPost())); leftLayout->addWidget(openButton, 0, Qt::AlignHCenter); } rightLowerLayout = new QHBoxLayout(); rightLowerLayout->setContentsMargins(0, 0, 0, 0); rightLowerLayout->addWidget(activityDescriptionLabel, 1); // This may also contain an AvatarButton for an "object person" rightLayout = new QVBoxLayout(); rightLayout->setAlignment(Qt::AlignTop); rightLayout->addWidget(timestampLabel); rightLayout->addLayout(rightLowerLayout); // If the object is a person, such as someone following someone else, add an AvatarButton for them if (activity->object()->getType() == "person") { ASPerson *personObject = activity->personObject(); if (personObject->getId() != authorId) // avoid cases like "JohnDoe updated JohnDoe" { this->haveTargetAvatar = true; this->targetAvatarButton = new AvatarButton(personObject, this->pController, this->globalObj, QSize(28,28), this); rightLowerLayout->addWidget(targetAvatarButton, 0, Qt::AlignBottom); } } mainLayout = new QHBoxLayout(); mainLayout->setContentsMargins(2, 1, 2, 4); if (authorId == pController->currentUserId()) { mainLayout->addLayout(rightLayout, 20); mainLayout->addLayout(leftLayout, 1); } else // Normal item, not ours { mainLayout->addLayout(leftLayout, 1); mainLayout->addLayout(rightLayout, 20); } this->setLayout(mainLayout); qDebug() << "MinorFeedItem created"; } MinorFeedItem::~MinorFeedItem() { qDebug() << "MinorFeedItem destroyed"; } /* * Pseudo-highlight for new items * */ void MinorFeedItem::setItemAsNew(bool isNew, bool informFeed) { itemIsNew = isNew; if (itemIsNew) { this->setAutoFillBackground(true); this->setBackgroundRole(QPalette::Mid); timestampLabel->setStyleSheet("QLabel " "{ background-color: qlineargradient(spread:pad," " x1:0, y1:0, x2:1, y2:0," " stop:0 palette(highlight)," " stop:0.2 palette(base)," " stop:0.8 palette(base)," " stop:1 palette(highlight));" "}" "QLabel:hover " "{ color: palette(highlighted-text); " " background-color: palette(highlight)" "}"); } else { this->setAutoFillBackground(false); this->setBackgroundRole(QPalette::Window); timestampLabel->setStyleSheet("QLabel:hover " "{ color: palette(highlighted-text); " " background-color: palette(highlight)" "}"); if (informFeed) { bool wasHighlighted = false; if (this->itemHighlightType != -1) { wasHighlighted = true; } emit itemRead(wasHighlighted); } } } bool MinorFeedItem::isNew() { return this->itemIsNew; } /* * Set/Update the fuzzy timestamp * * This will be called from time to time * */ void MinorFeedItem::setFuzzyTimeStamp() { this->timestampLabel->setText(Timestamp::fuzzyTime(createdAtTimestamp)); } void MinorFeedItem::syncAvatarFollowState() { this->avatarButton->syncFollowState(); if (haveTargetAvatar) { this->targetAvatarButton->syncFollowState(); } } int MinorFeedItem::getItemHighlightType() { return this->itemHighlightType; } QString MinorFeedItem::getActivityId() { return this->activityId; } /****************************************************************************/ /******************************** SLOTS *************************************/ /****************************************************************************/ void MinorFeedItem::openOriginalPost() { // Create a fake activity for the object QVariantMap originalPostMap; if (!inReplyToMap.isEmpty()) { originalPostMap.insert("object", this->inReplyToMap); originalPostMap.insert("actor", this->inReplyToMap.value("author").toMap()); } else { originalPostMap.insert("object", this->originalObjectMap); originalPostMap.insert("actor", this->originalObjectMap.value("author").toMap()); } ASActivity *originalPostActivity = new ASActivity(originalPostMap, this); Post *referencedPost = new Post(originalPostActivity, false, // Not highlighted true, // Post is standalone pController, globalObj, this->parentWidget()); // Pass parent widget (MinorFeed) instead // of 'this', so it won't be killed by reloads referencedPost->show(); connect(pController, SIGNAL(commentsReceived(QVariantList,QString)), referencedPost, SLOT(setAllComments(QVariantList,QString))); referencedPost->getAllComments(); } void MinorFeedItem::showUrlInfo(QString url) { if (!url.isEmpty()) { this->pController->showTransientMessage(url); qDebug() << "Link hovered in Minor Feed:" << url; } else { this->pController->showTransientMessage(""); } } /****************************************************************************/ /******************************* PROTECTED **********************************/ /****************************************************************************/ /* * On mouse click in any part of the item, set it as read * */ void MinorFeedItem::mousePressEvent(QMouseEvent *event) { if (itemIsNew) { this->setItemAsNew(false, // Mark as not new true); // Inform the feed // Avoid flickering of the "new" effect later this->timestampLabel->repaint(); } event->accept(); } /* * Ensure URL info in statusbar is hidden when the mouse leaves the item * */ void MinorFeedItem::leaveEvent(QEvent *event) { this->pController->showTransientMessage(""); event->accept(); } dianara-v1.3.2/src/listsmanager.h000664 000764 000764 00000005215 12511606714 016335 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef LISTSMANAGER_H #define LISTSMANAGER_H #include #include #include #include #include #include #include #include #include #include // TMP!! #include #include "pumpcontroller.h" #include "peoplewidget.h" class ListsManager : public QWidget { Q_OBJECT public: explicit ListsManager(PumpController *pumpController, QWidget *parent = 0); ~ListsManager(); void setListsList(QVariantList listsList); QTreeWidgetItem *createPersonItem(QString id, QString name, QString avatarFile); signals: public slots: void setPersonsInList(QVariantList personList, QString listUrl); void createList(); void deleteList(); void enableDisableCreateButton(QString listName); void enableDisableDeleteButtons(); void showAddPersonDialog(); void addPerson(QIcon icon, QString contactString, QString contactUrl); void removePerson(); void addPersonItemToList(QString personId, QString personName, QString avatarUrl); void removePersonItemFromList(QString personId); private: PumpController *pController; QVBoxLayout *mainLayout; QHBoxLayout *buttonsLayout; QTreeWidget *listsTreeWidget; QPushButton *deleteListButton; QPushButton *addPersonButton; QPushButton *removePersonButton; PeopleWidget *peopleWidget; QGroupBox *newListGroupbox; QHBoxLayout *groupboxMainLayout; QVBoxLayout *groupboxLeftLayout; QLineEdit *newListNameLineEdit; QTextEdit *newListDescTextEdit; QPushButton *createListButton; QStringList personListsUrlList; }; #endif // LISTSMANAGER_H dianara-v1.3.2/src/listsmanager.cpp000664 000764 000764 00000040700 12611531376 016670 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "listsmanager.h" ListsManager::ListsManager(PumpController *pumpController, QWidget *parent) : QWidget(parent) { this->pController = pumpController; connect(pController, SIGNAL(personListReceived(QVariantList,QString)), this, SLOT(setPersonsInList(QVariantList,QString))); connect(pController, SIGNAL(personAddedToList(QString,QString,QString)), this, SLOT(addPersonItemToList(QString,QString,QString))); connect(pController, SIGNAL(personRemovedFromList(QString)), this, SLOT(removePersonItemFromList(QString))); // To show the current lists this->listsTreeWidget = new QTreeWidget(this); listsTreeWidget->setColumnCount(2); listsTreeWidget->setHeaderLabels(QStringList() << tr("Name") << tr("Members")); listsTreeWidget->setSortingEnabled(true); listsTreeWidget->sortByColumn(0, Qt::AscendingOrder); listsTreeWidget->setAlternatingRowColors(true); listsTreeWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); connect(listsTreeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(enableDisableDeleteButtons())); // Buttons to add/remove people from lists, and the lists; Initially disabled this->addPersonButton = new QPushButton(QIcon::fromTheme("list-add-user", QIcon(":/images/list-add.png")), tr("Add Mem&ber"), this); addPersonButton->setDisabled(true); connect(addPersonButton, SIGNAL(clicked()), this, SLOT(showAddPersonDialog())); this->removePersonButton = new QPushButton(QIcon::fromTheme("list-remove-user", QIcon(":/images/list-remove.png")), tr("&Remove Member"), this); removePersonButton->setDisabled(true); connect(removePersonButton, SIGNAL(clicked()), this, SLOT(removePerson())); this->deleteListButton = new QPushButton(QIcon::fromTheme("edit-table-delete-row", QIcon(":/images/button-delete.png")), tr("&Delete Selected List"), this); deleteListButton->setDisabled(true); connect(deleteListButton, SIGNAL(clicked()), this, SLOT(deleteList())); // Groupbox for the "create new list" stuff this->newListGroupbox = new QGroupBox(tr("Add New &List"), this); this->newListNameLineEdit = new QLineEdit(this); newListNameLineEdit->setPlaceholderText(tr("Type a name for the new list...")); connect(newListNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(enableDisableCreateButton(QString))); connect(newListNameLineEdit, SIGNAL(returnPressed()), this, SLOT(createList())); this->newListDescTextEdit = new QTextEdit(this); newListDescTextEdit->setToolTip(tr("Type an optional description here")); newListDescTextEdit->setTabChangesFocus(true); this->createListButton = new QPushButton(QIcon::fromTheme("edit-table-insert-row-above", QIcon(":/images/list-add.png")), tr("Create L&ist"), this); createListButton->setDisabled(true); // Disabled until user types a name connect(createListButton, SIGNAL(clicked()), this, SLOT(createList())); // Widget to find and select a contact this->peopleWidget = new PeopleWidget(tr("&Add to List"), PeopleWidget::StandaloneWidget, this->pController, this); connect(peopleWidget, SIGNAL(addButtonPressed(QIcon,QString,QString)), this, SLOT(addPerson(QIcon,QString,QString))); // Layout this->buttonsLayout = new QHBoxLayout(); buttonsLayout->addWidget(addPersonButton, 0, Qt::AlignLeft); buttonsLayout->addWidget(removePersonButton, 0, Qt::AlignLeft); buttonsLayout->addStretch(1); buttonsLayout->addWidget(deleteListButton, 0, Qt::AlignRight); this->groupboxLeftLayout = new QVBoxLayout(); groupboxLeftLayout->addWidget(newListNameLineEdit); groupboxLeftLayout->addWidget(newListDescTextEdit); this->groupboxMainLayout = new QHBoxLayout(); groupboxMainLayout->addLayout(groupboxLeftLayout); groupboxMainLayout->addWidget(createListButton, 0, Qt::AlignBottom); newListGroupbox->setLayout(groupboxMainLayout); this->mainLayout = new QVBoxLayout(); mainLayout->addWidget(listsTreeWidget, 5); mainLayout->addLayout(buttonsLayout, 1); mainLayout->addStretch(1); mainLayout->addWidget(newListGroupbox, 3); this->setLayout(mainLayout); qDebug() << "ListsManager created"; } ListsManager::~ListsManager() { qDebug() << "ListsManager destroyed"; } void ListsManager::setListsList(QVariantList listsList) { qDebug() << "Setting person lists contents"; // Disable to avoid users messing with it while it loads this->setDisabled(true); this->listsTreeWidget->clear(); this->personListsUrlList.clear(); QString listName; QString listDescription; QString listMembersCount; QVariant listId; QVariant listUrl; foreach (QVariant list, listsList) { QVariantMap listMap = list.toMap(); listName = listMap.value("displayName").toString(); listDescription = listMap.value("content").toString(); if (!listDescription.isEmpty()) { listDescription.prepend(""); // HTMLize it so it gets wordwrapped } listMembersCount = listMap.value("members").toMap() .value("totalItems").toString(); listId = listMap.value("id"); listUrl = listMap.value("members").toMap().value("url"); qDebug() << "list ID:" << listId.toString(); qDebug() << "list URL:" << listUrl.toString(); QTreeWidgetItem *listItem = new QTreeWidgetItem(); listItem->setText(0, listName); listItem->setToolTip(0, listDescription); listItem->setText(1, listMembersCount); listItem->setToolTip(1, listDescription); listItem->setData(0, Qt::UserRole, listId); listItem->setData(0, Qt::UserRole + 1, listUrl); this->listsTreeWidget->addTopLevelItem(listItem); QString membersUrl = listMap.value("members").toMap() .value("url").toString(); this->personListsUrlList.append(membersUrl); } // Get list of people in each list foreach (QString url, personListsUrlList) { this->pController->getPersonList(url); } // this->listsTreeWidget->expandAll(); this->listsTreeWidget->resizeColumnToContents(0); // Re-enable this->setEnabled(true); } QTreeWidgetItem *ListsManager::createPersonItem(QString id, QString name, QString avatarFile) { QTreeWidgetItem *personItem = new QTreeWidgetItem(); avatarFile = MiscHelpers::getCachedAvatarFilename(avatarFile); QPixmap avatarPixmap = QPixmap(avatarFile); if (avatarPixmap.isNull()) { personItem->setIcon(0, QIcon::fromTheme("user-identity", QIcon(":/images/no-avatar.png"))); } else { personItem->setIcon(0, QIcon(avatarPixmap)); } personItem->setText(0, name); personItem->setText(1, id); return personItem; } /****************************************************************************/ /********************************* SLOTS ************************************/ /****************************************************************************/ /* * Fill one of the person lists with the names and ID's of the members * */ void ListsManager::setPersonsInList(QVariantList personList, QString listUrl) { qDebug() << "ListsManager::setPersonsInList()" << listUrl; // Find out which TreeWidgetItem matches this list QTreeWidgetItem *listItem = 0; // Avoid 'not initialized' warning foreach (QTreeWidgetItem *item, this->listsTreeWidget->findItems("", Qt::MatchContains)) { listItem = item; // Data in UserRole+1 is the Members Url if (listItem->data(0, Qt::UserRole + 1).toString() == listUrl) { break; } } foreach (QVariant personVariant, personList) { // FIXME: this should use ASPerson() QVariantMap personMap = personVariant.toMap(); QString id = ASPerson::cleanupId(personMap.value("id").toString()); QString name = personMap.value("displayName").toString(); QString avatar = personMap.value("image").toMap().value("url").toString(); if (listItem != 0) // Ensure it's initialized! { QTreeWidgetItem *childItem = this->createPersonItem(id, name, avatar); listItem->addChild(childItem); } } } void ListsManager::createList() { QString listName = this->newListNameLineEdit->text().trimmed(); QString listDescription = this->newListDescTextEdit->toPlainText().trimmed(); listDescription.replace("\n", "
              "); if (!listName.isEmpty()) { qDebug() << "Creating list:" << listName; this->pController->createPersonList(listName, listDescription); this->newListNameLineEdit->clear(); this->newListDescTextEdit->clear(); } else { qDebug() << "Error: List name is empty!"; } } void ListsManager::deleteList() { if (listsTreeWidget->currentItem() == 0) // if nothing selected, so NULL... { return; } QString listName = this->listsTreeWidget->currentItem()->text(0); int confirmation = QMessageBox::question(this, tr("WARNING: Delete list?"), tr("Are you sure you want to delete %1?", "1=Name of a person list").arg(listName), tr("&Yes, delete it"), tr("&No"), "", 1, 1); if (confirmation == 0) { // Avoid the possibility of deleting twice! this->deleteListButton->setDisabled(true); QString listId = this->listsTreeWidget->currentItem()->data(0, Qt::UserRole).toString(); this->pController->deletePersonList(listId); } else { qDebug() << "Confirmation cancelled, not deleting the list"; } } /* * Enable or disable the "Create List" button * */ void ListsManager::enableDisableCreateButton(QString listName) { if (listName.trimmed().isEmpty()) { this->createListButton->setDisabled(true); } else { this->createListButton->setEnabled(true); } } /* * Enable or disable the "remove person" and "delete list" buttons * according to what's currently selected * */ void ListsManager::enableDisableDeleteButtons() { // A list is selected if (listsTreeWidget->currentItem()->parent() == 0) { this->deleteListButton->setEnabled(true); this->removePersonButton->setDisabled(true); } else // One of the items inside a list is selected { this->deleteListButton->setDisabled(true); this->removePersonButton->setEnabled(true); } // Either way... this->addPersonButton->setEnabled(true); } void ListsManager::showAddPersonDialog() { if (listsTreeWidget->currentItem() == 0) // No list selected { return; } // Show the people widget, which will return the selected contact in a SIGNAL this->peopleWidget->resize(400, this->height()); this->peopleWidget->resetWidget(); this->peopleWidget->show(); } void ListsManager::addPerson(QIcon icon, QString contactString, QString contactUrl) { Q_UNUSED(icon) Q_UNUSED(contactUrl) this->peopleWidget->hide(); QRegExp contactRE("(.+)\\s+\\<(.+@.+)\\>", Qt::CaseInsensitive); contactRE.setMinimal(true); contactRE.indexIn(contactString); QString personId = contactRE.cap(2).trimmed(); // The part between < and > if (personId.isEmpty()) { return; } QString listId; if (listsTreeWidget->currentItem()->parent() == 0) // If a list is selected { listId = this->listsTreeWidget->currentItem()->data(0, Qt::UserRole).toString(); } else // If a member is selected, get its matching list { listId = this->listsTreeWidget->currentItem()->parent()->data(0, Qt::UserRole).toString(); } this->setDisabled(true); pController->addPersonToList(listId, "acct:" + personId); } void ListsManager::removePerson() { if (listsTreeWidget->currentItem() == 0) // Nothing selected, so it's NULL { return; } QString personId = this->listsTreeWidget->currentItem()->text(1); QString personName = this->listsTreeWidget->currentItem()->text(0); if (personName.isEmpty()) { personName = personId; } QString listId; QString listName; if (listsTreeWidget->currentItem()->parent() != 0) // Ensure it's not a list { listId = this->listsTreeWidget->currentItem()->parent()->data(0, Qt::UserRole).toString(); listName = this->listsTreeWidget->currentItem()->parent()->text(0); } int confirmation = QMessageBox::question(this, tr("Remove person from list?"), tr("Are you sure you want to remove %1 " "from the %2 list?", "1=Name of a person, " "2=name of a list") .arg(personName).arg(listName), tr("&Yes"), tr("&No"), "", 1, 1); if (confirmation == 0) { this->setDisabled(true); this->pController->removePersonFromList(listId, "acct:" + personId); } else { qDebug() << "Confirmation cancelled, not removing" << personName << "from" << listName; } } /* * Add the new item itself after a person is added correctly in PumpController * */ void ListsManager::addPersonItemToList(QString personId, QString personName, QString avatarUrl) { QTreeWidgetItem *newItem = this->createPersonItem(personId, personName, avatarUrl); QTreeWidgetItem *selectedItem = this->listsTreeWidget->currentItem(); // When the list itself is NOT selected, but a child, point to the parent if (selectedItem->parent() != 0) { selectedItem = selectedItem->parent(); } selectedItem->addChild(newItem); // Update member counter selectedItem->setText(1, QString("%1").arg(selectedItem->childCount())); this->setEnabled(true); } void ListsManager::removePersonItemFromList(QString personId) { if (personId == listsTreeWidget->currentItem()->text(1)) { QTreeWidgetItem *parentList = listsTreeWidget->currentItem()->parent(); delete listsTreeWidget->currentItem(); // Update member counter parentList->setText(1, QString("%1").arg(parentList->childCount())); } this->setEnabled(true); } dianara-v1.3.2/src/filtereditor.h000664 000764 000764 00000004242 12451104250 016326 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef FILTEREDITOR_H #define FILTEREDITOR_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filterchecker.h" class FilterEditor : public QWidget { Q_OBJECT public: explicit FilterEditor(FilterChecker *filterChecker, QWidget *parent = 0); ~FilterEditor(); void loadFilters(); signals: public slots: void addFilter(); void removeFilter(); void saveFilters(); private: QVBoxLayout *mainLayout; QHBoxLayout *topLayout; QGroupBox *newFilterGroupBox; QVBoxLayout *middleLayout; QGroupBox *currentFiltersGroupBox; QHBoxLayout *bottomLayout; QAction *closeAction; QLabel *explanationLabel; QComboBox *actionTypeComboBox; QComboBox *filterTypeComboBox; QLineEdit *filterWordsLineEdit; QPushButton *addFilterButton; QListWidget *currentFiltersListWidget; QPushButton *removeFilterButton; QPushButton *saveButton; QPushButton *cancelButton; QString ruleString; QVariantList filtersList; FilterChecker *fChecker; }; #endif // FILTEREDITOR_H dianara-v1.3.2/src/asactivity.h000664 000764 000764 00000004351 12451104252 016015 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef ASACTIVITY_H #define ASACTIVITY_H #include #include #include #include #include #include "asobject.h" #include "asperson.h" #include "mischelpers.h" class ASActivity : public QObject { Q_OBJECT public: explicit ASActivity(QVariantMap activityMap, QObject *parent = 0); ~ASActivity(); ASPerson *author(); ASObject *object(); ASObject *target(); ASPerson *personObject(); QString getId(); QString getVerb(); QString getGenerator(); QString getCreatedAt(); QString getUpdatedAt(); QString getContent(); QString getToString(); QString getCCString(); QStringList getRecipientsIdList(); bool isShared(); QString getSharedByName(); QString getSharedById(); QString getSharedByAvatar(); QString generateTooltip(); QString generateSnippet(int charLimit); signals: public slots: private: ASPerson *asAuthor; ASObject *asObject; ASObject *asTarget; ASPerson *asPersonObject; QString id; QString verb; QString generator; QString createdAt; QString updatedAt; QString content; QString recipientsToString; QString recipientsCcString; QStringList recipientsIdList; bool shared; QString sharedByName; QString sharedById; QString sharedByAvatar; }; #endif // ASACTIVITY_H dianara-v1.3.2/src/asobject.cpp000664 000764 000764 00000037251 12600533471 015774 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "asobject.h" ASObject::ASObject(QVariantMap objectMap, QObject *parent) : QObject(parent) { this->originalObjectMap = objectMap; /// Meta information id = ASPerson::cleanupId(objectMap.value("id").toString()); type = objectMap.value("objectType").toString(); url = objectMap.value("url").toString(); createdAt = objectMap.value("published").toString(); updatedAt = objectMap.value("updated").toString(); // FIXME: still missing some fields... QVariantMap locationMap = objectMap.value("location").toMap(); locationName = locationMap.value("displayName").toString(); locationFormatted = locationMap.value("address").toMap().value("formatted").toString(); locationCountry = locationMap.value("address").toMap().value("country").toString(); // Author this->asAuthor = new ASPerson(objectMap.value("author").toMap(), this); // This will hold the date when the object was deleted, or empty if it wasn't deleted = objectMap.value("deleted").toString(); liked = objectMap.value("liked").toString(); // The object to which this one replies, if any (note to which a comment replies, etc) inReplyToMap = objectMap.value("inReplyTo").toMap(); /// /// End of "meta"; Start of content /// if (type == "image") { // See if there's a proxy URL for the image first (for private images) imageUrl = objectMap.value("fullImage").toMap().value("pump_io").toMap().value("proxyURL").toString(); qDebug() << "Trying Proxyed fullImage"; // if that's empty, try the "small" version if (imageUrl.isEmpty()) { qDebug() << "Trying Proxyed thumbnail image"; imageUrl = objectMap.value("image").toMap().value("pump_io").toMap().value("proxyURL").toString(); } // If that's also empty, use regular fullImage->url field if (imageUrl.isEmpty()) { qDebug() << "Trying direct fullImage"; imageUrl = objectMap.value("fullImage").toMap().value("url").toString(); } // And if that is ALSO empty, use regular image->url if (imageUrl.isEmpty()) { qDebug() << "Trying direct thumbnail image"; imageUrl = objectMap.value("image").toMap().value("url").toString(); } qDebug() << "postImage:" << imageUrl; // To have a filename with extension, even when downloading from proxyURL's attachmentPureUrl = objectMap.value("fullImage").toMap().value("url").toString(); if (attachmentPureUrl.isEmpty()) { attachmentPureUrl = objectMap.value("image").toMap().value("url").toString(); } } // Get audio file URL if (type == "audio") { // To have a filename with extension, even when downloading from proxyURL's attachmentPureUrl = objectMap.value("stream").toMap().value("url").toString(); audioUrl = objectMap.value("stream").toMap().value("pump_io").toMap().value("proxyURL").toString(); if (audioUrl.isEmpty()) { qDebug() << "No proxyed link to audio, fetching regular link"; audioUrl = attachmentPureUrl; } } // Get video file URL if (type == "video") { // To have a filename with extension, even when downloading from proxyURL's attachmentPureUrl = objectMap.value("stream").toMap().value("url").toString(); videoUrl = objectMap.value("stream").toMap().value("pump_io").toMap().value("proxyURL").toString(); if (videoUrl.isEmpty()) { qDebug() << "No proxyed link to video, fetching regular link"; videoUrl = attachmentPureUrl; } } // Get general file URL if (type == "file") { // To have a filename with extension, even when downloading from proxyURL's attachmentPureUrl = objectMap.value("fileUrl").toString(); // FIXME: there are no proxyURL for misc files ATM; not my fault though =) fileUrl = objectMap.value("fileUrl").toMap().value("pump_io").toMap().value("proxyURL").toString(); if (fileUrl.isEmpty()) { qDebug() << "No proxyed link to general file, fetching regular link"; fileUrl = attachmentPureUrl; } mimeType = objectMap.value("mimeType").toString(); qDebug() << "file mimeType:" << mimeType; } // If it's a group, get member count, etc. if (type == "group") { memberCount = objectMap.value("members").toMap().value("totalItems").toInt(); memberUrl = objectMap.value("members").toMap().value("url").toString(); } // Title can be in non-image posts, too! title = objectMap.value("displayName").toString().trimmed(); summary = objectMap.value("summary").toString(); content = objectMap.value("content").toString(); likesCount = objectMap.value("likes").toMap().value("totalItems").toString(); if (likesCount.isEmpty()) { likesCount = "0"; } commentsCount = objectMap.value("replies").toMap().value("totalItems").toString(); sharesCount = objectMap.value("shares").toMap().value("totalItems").toString(); // Get last likes, comments and shares list here, used from Post() lastLikesList = objectMap.value("likes").toMap().value("items").toList(); lastCommentsList = objectMap.value("replies").toMap().value("items").toList(); lastSharesList = objectMap.value("shares").toMap().value("items").toList(); proxiedUrls = false; // Get URL for likes; first, proxyURL if it exists likesUrl = objectMap.value("likes").toMap().value("pump_io").toMap() .value("proxyURL").toString(); // If still empty, get regular URL (that means the post is in the same server we are) if (likesUrl.isEmpty()) { likesUrl = objectMap.value("likes").toMap().value("url").toString(); } // Get URL for comments; first, proxyURL if it exists commentsUrl = objectMap.value("replies").toMap().value("pump_io").toMap() .value("proxyURL").toString(); if (commentsUrl.isEmpty()) // If still empty, get regular URL { commentsUrl = objectMap.value("replies").toMap().value("url").toString(); } else { this->proxiedUrls = true; } // FIXME: get sharesUrl... qDebug() << "ASObject created" << this->id; } ASObject::~ASObject() { qDebug() << "ASObject destroyed" << this->id; } void ASObject::updateAuthorFromPerson(ASPerson *person) { this->asAuthor->updateDataFromPerson(person); } /////// Getters ASPerson *ASObject::author() { return this->asAuthor; } QString ASObject::getId() { return this->id; } QString ASObject::getType() { return this->type; } QString ASObject::getTranslatedType(QString typeString) { QString translatedTypeString; if (typeString == "note") { translatedTypeString = tr("Note", "Noun, an object type"); } else if (typeString == "article") { translatedTypeString = tr("Article", "Noun, an object type"); } else if (typeString == "image") { translatedTypeString = tr("Image", "Noun, an object type"); } else if (typeString == "audio") { translatedTypeString = tr("Audio", "Noun, an object type"); } else if (typeString == "video") { translatedTypeString = tr("Video", "Noun, an object type"); } else if (typeString == "file") { translatedTypeString = tr("File", "Noun, an object type"); } else if (typeString == "comment") { translatedTypeString = tr("Comment", "Noun, as in object type: a comment"); } else if (typeString == "group") { translatedTypeString = tr("Group", "Noun, an object type"); } else if (typeString == "collection") { translatedTypeString = tr("Collection", "Noun, an object type"); } else { translatedTypeString = tr("Other", "As in: other type of post") + " (" + typeString + ")"; } return translatedTypeString; } QString ASObject::getUrl() { return this->url; } QString ASObject::getCreatedAt() { return this->createdAt; } QString ASObject::getUpdatedAt() { return this->updatedAt; } QString ASObject::getLocationName() { return this->locationName; } QString ASObject::getLocationFormatted() { return this->locationFormatted; } QString ASObject::getLocationCountry() { return this->locationCountry; } QString ASObject::getLocationTooltip() { QString locationTooltip = this->locationFormatted; if (!this->locationCountry.isEmpty()) { if (!locationTooltip.isEmpty()) { locationTooltip.append("\n"); } locationTooltip.append("(" + this->locationCountry + ")"); } // If still empty, return informational string if (locationTooltip.isEmpty()) { locationTooltip = tr("No detailed location"); } return locationTooltip; } QString ASObject::getDeletedTime() { return this->deleted; } QString ASObject::getDeletedOnString() { QString deletedOnString; if (!this->deleted.isEmpty()) { deletedOnString = this->makeDeletedOnString(this->deleted); } return deletedOnString; } QString ASObject::makeDeletedOnString(QString deletionTime) { return tr("Deleted on %1").arg(Timestamp::localTimeDate(deletionTime)); } QString ASObject::isLiked() { return this->liked; } QString ASObject::getTitle() { return this->title; } QString ASObject::getSummary() { return this->summary; } QString ASObject::getContent() { return this->content; } QString ASObject::getImageUrl() { return this->imageUrl; } QString ASObject::getAudioUrl() { return this->audioUrl; } QString ASObject::getVideoUrl() { return this->videoUrl; } QString ASObject::getFileUrl() { return this->fileUrl; } QString ASObject::getMimeType() { return this->mimeType; } QString ASObject::getAttachmentPureUrl() { return this->attachmentPureUrl; } int ASObject::getMemberCount() { return this->memberCount; } QString ASObject::getMemberUrl() { return this->memberUrl; } QString ASObject::getLikesCount() { return this->likesCount; } QString ASObject::getCommentsCount() { return this->commentsCount; } QString ASObject::getSharesCount() { return this->sharesCount; } bool ASObject::hasProxiedUrls() { return this->proxiedUrls; } QVariantList ASObject::getLastLikesList() { return this->lastLikesList; } QVariantList ASObject::getLastCommentsList() { return this->lastCommentsList; } QVariantList ASObject::getLastSharesList() { return this->lastSharesList; } QString ASObject::getLikesUrl() { return this->likesUrl; } QString ASObject::getCommentsUrl() { return this->commentsUrl; } QString ASObject::getSharesUrl() { return this->sharesUrl; } QVariantMap ASObject::getOriginalObject() { return this->originalObjectMap; } QVariantMap ASObject::getInReplyTo() { return this->inReplyToMap; } QString ASObject::getInReplyToId() { return inReplyToMap.value("id").toString(); } /* * This version should be deprecated soon -- WIP * */ QString ASObject::personStringFromList(QVariantList variantList, int count) { QString personString; foreach (QVariant listItem, variantList) { QVariantMap itemMap = listItem.toMap(); QString name = itemMap.value("displayName").toString(); if (name.isEmpty()) { name = ASPerson::cleanupId(itemMap.value("id").toString()); // fallback } QString profileUrl = itemMap.value("url").toString(); personString.prepend("" + name + ", "); // Reverse order } int remainingCount = count - variantList.size(); // Add "and $COUNT other" if (count != -1 && remainingCount > 0) { if (remainingCount == 1) { personString.append(tr("and one other")); } else { personString.append(tr("and %1 others").arg(remainingCount)); } } else { // Remove last comma and space personString.remove(-2, 2); } return personString; } // deprecating... WIP QVariantMap ASObject::simplePersonMapFromList(QVariantList variantList) { QVariantMap map; // Read the list in reverse order: for (int position = variantList.size() - 1; position >= 0; --position) { QVariantMap itemMap = variantList.at(position).toMap(); QString id = ASPerson::cleanupId(itemMap.value("id").toString()); QString name = itemMap.value("displayName").toString(); if (name.isEmpty()) { name = id; // fallback } QString profileUrl = itemMap.value("url").toString(); ASObject::addOnePersonToSimpleMap(id, name, profileUrl, &map); } return map; } void ASObject::addOnePersonToSimpleMap(QString personId, QString personName, QString personUrl, QVariantMap *personMap) { personMap->insert(personId, "" + personName + ""); } QString ASObject::personStringFromSimpleMap(QVariantMap personMap, int totalCount) { QString personString; foreach (QString key, personMap.keys()) { personString.append(personMap.value(key).toString() + ", "); } int remainingCount = totalCount - personMap.keys().length(); // Add "and $COUNT other" if (remainingCount > 0) { if (remainingCount == 1) { personString.append(tr("and one other")); } else { personString.append(tr("and %1 others").arg(remainingCount)); } } else { // Remove last comma and space personString.remove(-2, 2); } return personString; } /* * Returns true if objectType is one of the types that Post() can display * * */ bool ASObject::canDisplayObject(QString objectType) { bool canDisplay = false; if (objectType == "note" || objectType == "article" || objectType == "image" || objectType == "audio" || objectType == "video" || objectType == "file" || objectType == "comment" || objectType == "group") { canDisplay = true; } else { qDebug() << "ASObject::canDisplayObject() - Unsupported:" << objectType; } return canDisplay; } dianara-v1.3.2/src/asactivity.cpp000664 000764 000764 00000027621 12511056441 016360 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "asactivity.h" ASActivity::ASActivity(QVariantMap activityMap, QObject *parent) : QObject(parent) { id = activityMap.value("id").toString(); // Get the author of the _activity_ (not the object) this->asAuthor = new ASPerson(activityMap.value("actor").toMap(), this); // Get the object included with the activity: the post itself, or whatever this->asObject = new ASObject(activityMap.value("object").toMap(), this); // If the object is a person, as in an activity where someone adds someone else if (asObject->getType() == "person") { this->asPersonObject = new ASPerson(activityMap.value("object").toMap(), this); } else { // Otherwise, at least initialize empty, but valid this->asPersonObject = new ASPerson(QVariantMap(), this); } // Get possible "target" this->asTarget = new ASObject(activityMap.value("target").toMap(), this); // FIXME: add bools hasAuthor, hasObject and hasTarget, maybe? // Activity verb: post, share... verb = activityMap.value("verb").toString(); if (verb == "share") { shared = true; sharedByName = asAuthor->getNameWithFallback(); sharedById = asAuthor->getId(); sharedByAvatar = asAuthor->getAvatar(); } else { shared = false; } // Timestamps createdAt = activityMap.value("published").toString(); updatedAt = activityMap.value("updated").toString(); // Software used to generate the activity: webUI, a client, a service... generator = activityMap.value("generator").toMap().value("displayName").toString(); // Content of the activity, usually a description like "User followed someone" content = activityMap.value("content").toString(); // Audience: To QVariantMap aToMap; foreach (QVariant toVariant, activityMap.value("to").toList()) { aToMap = toVariant.toMap(); QString toId = aToMap.value("id").toString(); if (toId == "http://activityschema.org/collection/public") { recipientsToString += "" + tr("Public") + ", "; } else { QString displayName = aToMap.value("displayName").toString().trimmed(); // If name's empty, and ID is from a person if (displayName.isEmpty() && toId.startsWith("acct:")) { displayName = "<" + ASPerson::cleanupId(toId) + ">"; } #ifdef SHOWWRONGTOCC if (displayName.isEmpty()) // if STILL empty, some weirdness is there { displayName = "-?-"; } #endif if (!displayName.isEmpty()) { recipientsToString += "" + displayName + ", "; } recipientsIdList.append(toId); } } recipientsToString.remove(-2, 2); // remove last comma and space // and Cc QVariantMap aCcMap; foreach (QVariant ccVariant, activityMap.value("cc").toList()) { aCcMap = ccVariant.toMap(); QString ccId = aCcMap.value("id").toString(); if (ccId == "http://activityschema.org/collection/public") { recipientsCcString += "" + tr("Public") + ", "; } else { QString displayName = aCcMap.value("displayName").toString().trimmed(); // If name's empty, and ID is from a person if (displayName.isEmpty() && ccId.startsWith("acct:")) { displayName = "<" + ASPerson::cleanupId(ccId) + ">"; } #ifdef SHOWWRONGTOCC if (displayName.isEmpty()) // if STILL empty, for some reason... { displayName = "-?-"; } #endif if (!displayName.isEmpty()) { recipientsCcString += "" + displayName + ", "; } recipientsIdList.append(ccId); } } recipientsCcString.remove(-2, 2); qDebug() << "ASActivity created" << this->id; } ASActivity::~ASActivity() { qDebug() << "ASActivity destroyed" << this->id; } ////// Getters! ASPerson *ASActivity::author() { return this->asAuthor; } ASObject *ASActivity::object() { return this->asObject; } ASObject *ASActivity::target() { return this->asTarget; } ASPerson *ASActivity::personObject() { return this->asPersonObject; } QString ASActivity::getId() { return this->id; } QString ASActivity::getVerb() { return this->verb; } QString ASActivity::getGenerator() { return this->generator; } QString ASActivity::getCreatedAt() { return this->createdAt; } QString ASActivity::getUpdatedAt() { return this->updatedAt; } QString ASActivity::getContent() { return this->content; } QString ASActivity::getToString() { return this->recipientsToString; } QString ASActivity::getCCString() { return this->recipientsCcString; } QStringList ASActivity::getRecipientsIdList() { return this->recipientsIdList; } bool ASActivity::isShared() { return this->shared; } QString ASActivity::getSharedByName() { return this->sharedByName; } QString ASActivity::getSharedById() { return this->sharedById; } QString ASActivity::getSharedByAvatar() { return this->sharedByAvatar; } /* * Create a string to be used as a tooltip * */ QString ASActivity::generateTooltip() { QString activityTooltip; // Content QString activityObjectContent = this->asObject->getContent(); if (this->asObject->getDeletedTime().isEmpty()) { QString objectType = this->asObject->getType(); if (ASObject::canDisplayObject(objectType)) // False also for empty type { if (!activityObjectContent.startsWith(", so add extra newline activityObjectContent.prepend("
              "); } activityObjectContent.prepend("[ " + ASObject::getTranslatedType(objectType) + " ]
              "); } } else { activityObjectContent.prepend("[" + this->asObject->getDeletedOnString() + "]"); } if (!activityObjectContent.isEmpty()) { // Object's author name and ID QString activityObjectAuthorName = this->asObject->author()->getNameWithFallback(); QString activityObjectAuthorId = this->asObject->author()->getId(); // If there's a name and/or an ID, show them if (!activityObjectAuthorName.isEmpty()) { activityTooltip = "" + activityObjectAuthorName + "
              " "" + activityObjectAuthorId + "" "

              "; // horizontal rule as separator } // Title, only if there's content, too; redundant otherwise QString activityObjectTitle = this->asObject->getTitle(); if (!activityObjectTitle.isEmpty()) { activityTooltip.append("" + activityObjectTitle + "" "

              "); } activityTooltip.append(activityObjectContent + "
              "); // Object ID, might be interesting to show if it's a person if (this->asObject->getType() == "person") { activityTooltip.append("
              " + this->asObject->getId() + "

              "); } } // Target info QString activityTargetName = this->asTarget->getTitle(); QString activityTargetUrl = this->asTarget->getUrl(); if (activityTargetUrl.isEmpty()) { activityTargetUrl = this->asTarget->getId(); } if (!activityTargetUrl.isEmpty()) { activityTooltip.append("
              >> "); // Horizontal rule, and >> if (!activityTargetName.isEmpty()) { activityTooltip.append(QString("%1
              %2") .arg(activityTargetName) .arg(activityTargetUrl)); } else { activityTooltip.append("" + activityTargetUrl + ""); } // Show type of target object activityTooltip.append("
              " + ASObject::getTranslatedType(asTarget->getType())); } // Remove last
              , if needed if (activityTooltip.endsWith("
              ")) { activityTooltip.remove(-4, 4); // Check again, for double
              's if (activityTooltip.endsWith("
              ")) { activityTooltip.remove(-4, 4); } } activityTooltip = activityTooltip.trimmed(); // If there's something, ensure it gets treated as HTML if (!activityTooltip.isEmpty()) { activityTooltip.prepend(""); } return activityTooltip; } /* * Create a snippet of the activity and its object * */ QString ASActivity::generateSnippet(int charLimit) { QString snippet; QString authorName = this->asObject->author()->getNameWithFallback(); // If author name IS empty, probably means the author of the activity is the // author of the object (like posting a comment), so this wouldn't be useful if (this->asObject->author()->getId() != this->author()->getId() && !authorName.isEmpty()) { snippet.append("

              " // Inside a

              to avoid a weird newline bug + tr("%1 by %2", "1=kind of object: note, comment, etc; " "2=author's name") .arg(ASObject::getTranslatedType(this->asObject->getType())) .arg("" + authorName + "") + ":

              "); } QString objectContent = this->asObject->getContent(); if (!objectContent.isEmpty()) { /* //// Disabled: Showing the title is always redundant //// considering how the Pump.io core generates the //// activities descriptions. QString objectTitle = this->asObject->getTitle(); if (!objectTitle.isEmpty()) { snippet.append("" + objectTitle + "
              "); } */ QTextDocument textDocument; textDocument.setHtml(objectContent); // Limit characters in snippet if (textDocument.characterCount() > charLimit) { QTextCursor cursor = textDocument.find(QRegExp(".*"), charLimit); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.removeSelectedText(); cursor.insertHtml("  [...]"); } snippet.append(MiscHelpers::cleanupHtml(textDocument.toHtml())); } return snippet.trimmed(); } dianara-v1.3.2/src/filtereditor.cpp000664 000764 000764 00000023746 12451104247 016701 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "filtereditor.h" FilterEditor::FilterEditor(FilterChecker *filterChecker, QWidget *parent) : QWidget(parent) { this->fChecker = filterChecker; this->setWindowTitle(tr("Filter Editor") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("view-filter", QIcon(":/images/button-filter.png"))); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::ApplicationModal); this->setMinimumSize(520, 400); ruleString = tr("%1 if %2 contains: %3", "This explains a filter rule, like: " "Hide if Author ID contains JohnDoe"); QFont explanationFont; explanationFont.setPointSize(explanationFont.pointSize() - 1); explanationLabel = new QLabel(tr("Here you can set some rules for hiding or " "highlighting stuff. " "You can filter by content, author " "or application.\n\n" "For instance, you can filter out messages posted by " "the application Open Farm Game, or which contain the " "word NSFW in the message. You could also highlight " "messages that contain your name.") + "\n"); explanationLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); explanationLabel->setWordWrap(true); explanationLabel->setFont(explanationFont); actionTypeComboBox = new QComboBox(); actionTypeComboBox->addItem(QIcon::fromTheme("edit-delete", QIcon(":/images/button-delete.png")), tr("Hide")); actionTypeComboBox->addItem(QIcon::fromTheme("format-fill-color", QIcon(":/images/button-online.png")), tr("Highlight")); actionTypeComboBox->setCurrentIndex(1); // #2 option (highlight) as default filterTypeComboBox = new QComboBox(); filterTypeComboBox->addItem(QIcon::fromTheme("accessories-text-editor", QIcon(":/images/button-edit.png")), tr("Post Contents")); filterTypeComboBox->addItem(QIcon::fromTheme("user-identity", QIcon(":/images/no-avatar.png")), tr("Author ID")); filterTypeComboBox->addItem(QIcon::fromTheme("applications-other", QIcon(":/images/button-configure.png")), tr("Application")); filterTypeComboBox->addItem(QIcon::fromTheme("accessories-text-editor", QIcon(":/images/button-edit.png")), tr("Activity Description")); filterWordsLineEdit = new QLineEdit(); filterWordsLineEdit->setPlaceholderText(tr("Keywords...")); addFilterButton = new QPushButton(QIcon::fromTheme("list-add", QIcon(":/images/list-add.png")), tr("&Add Filter")); connect(addFilterButton, SIGNAL(clicked()), this, SLOT(addFilter())); connect(filterWordsLineEdit, SIGNAL(returnPressed()), this, SLOT(addFilter())); currentFiltersListWidget = new QListWidget(); currentFiltersListWidget->setToolTip(tr("Filters in use")); removeFilterButton = new QPushButton(QIcon::fromTheme("list-remove", QIcon(":/images/list-remove.png")), tr("&Remove Selected Filter")); connect(removeFilterButton, SIGNAL(clicked()), this, SLOT(removeFilter())); saveButton = new QPushButton(QIcon::fromTheme("document-save", QIcon(":/images/button-save.png")), tr("&Save Filters")); connect(saveButton, SIGNAL(clicked()), this, SLOT(saveFilters())); cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("&Cancel")); connect(cancelButton, SIGNAL(clicked()), this, SLOT(hide())); closeAction = new QAction(this); closeAction->setShortcut(QKeySequence(Qt::Key_Escape)); connect(closeAction, SIGNAL(triggered()), this, SLOT(hide())); this->addAction(closeAction); // Layout topLayout = new QHBoxLayout(); topLayout->addWidget(actionTypeComboBox); topLayout->addSpacing(2); topLayout->addWidget(new QLabel(tr("if"))); topLayout->addSpacing(2); topLayout->addWidget(filterTypeComboBox); topLayout->addSpacing(2); topLayout->addWidget(new QLabel(tr("contains"))); topLayout->addSpacing(2); topLayout->addWidget(filterWordsLineEdit, 1); topLayout->addSpacing(8); topLayout->addWidget(addFilterButton); newFilterGroupBox = new QGroupBox(tr("&New Filter")); newFilterGroupBox->setLayout(topLayout); middleLayout = new QVBoxLayout(); middleLayout->addWidget(currentFiltersListWidget); middleLayout->addWidget(removeFilterButton, 0, Qt::AlignRight); currentFiltersGroupBox = new QGroupBox(tr("C&urrent Filters")); currentFiltersGroupBox->setLayout(middleLayout); bottomLayout = new QHBoxLayout(); bottomLayout->setAlignment(Qt::AlignRight); bottomLayout->addWidget(saveButton); bottomLayout->addWidget(cancelButton); mainLayout = new QVBoxLayout(); mainLayout->addWidget(explanationLabel); mainLayout->addSpacing(8); mainLayout->addWidget(newFilterGroupBox); mainLayout->addSpacing(8); mainLayout->addWidget(currentFiltersGroupBox); mainLayout->addSpacing(8); mainLayout->addLayout(bottomLayout); this->setLayout(mainLayout); loadFilters(); qDebug() << "FilterEditor created"; } FilterEditor::~FilterEditor() { qDebug() << "FilterEditor destroyed"; } void FilterEditor::loadFilters() { QSettings settings; this->filtersList = settings.value("Filters/currentFilters").toList(); foreach (QVariant filterItem, filtersList) { int actionType = filterItem.toMap().value("action").toInt(); int filterType = filterItem.toMap().value("type").toInt(); QString filterWords = filterItem.toMap().value("text").toString(); QVariantMap itemMap; itemMap.insert("action", actionType); itemMap.insert("type", filterType); itemMap.insert("text", filterWords); QListWidgetItem *item = new QListWidgetItem(ruleString .arg(actionTypeComboBox->itemText(actionType)) .arg(filterTypeComboBox->itemText(filterType)) .arg(filterWords)); item->setData(Qt::UserRole, itemMap); this->currentFiltersListWidget->addItem(item); } // Let the FilterChecker know this->fChecker->setFilters(filtersList); } /****************************************************************************/ /********************************* SLOTS ************************************/ /****************************************************************************/ void FilterEditor::addFilter() { QString words = this->filterWordsLineEdit->text().trimmed(); if (words.isEmpty()) { return; } QVariantMap itemMap; itemMap.insert("action", actionTypeComboBox->currentIndex()); itemMap.insert("type", filterTypeComboBox->currentIndex()); itemMap.insert("text", words); QListWidgetItem *item = new QListWidgetItem(ruleString .arg(actionTypeComboBox->currentText()) .arg(filterTypeComboBox->currentText()) .arg(words)); item->setData(Qt::UserRole, itemMap); currentFiltersListWidget->addItem(item); filterWordsLineEdit->clear(); } void FilterEditor::removeFilter() { int selectedFilter = currentFiltersListWidget->currentRow(); qDebug() << "FilterEditor::removeFilter()" << selectedFilter; if (selectedFilter != -1) { QListWidgetItem *removedItem = currentFiltersListWidget->takeItem(selectedFilter); delete removedItem; } } void FilterEditor::saveFilters() { qDebug() << "FilterEditor::saveFilters()"; filtersList.clear(); for (int counter = 0; counter != currentFiltersListWidget->count(); ++counter) { QListWidgetItem *item = currentFiltersListWidget->item(counter); filtersList.append(item->data(Qt::UserRole)); // item->data() holds a QVariantMap with the filter } qDebug() << "filtersList: " << this->filtersList; QSettings settings; settings.setValue("Filters/currentFilters", filtersList); // Send to the FilterChecker too this->fChecker->setFilters(filtersList); this->hide(); } dianara-v1.3.2/src/peoplewidget.h000664 000764 000764 00000004723 12461231650 016334 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef PEOPLEWIDGET_H #define PEOPLEWIDGET_H #include #include #include #include #include #include #include #include #include #include "pumpcontroller.h" class PeopleWidget : public QWidget { Q_OBJECT public: explicit PeopleWidget(QString buttonText, int type, PumpController *pumpController, QWidget *parent = 0); ~PeopleWidget(); void resetWidget(); QStandardItem *createContactItem(ASPerson *contact); enum WidgetType { EmbeddedWidget, StandaloneWidget }; signals: void contactSelected(QIcon contactIcon, QString contactString, QString contactUrl); void addButtonPressed(QIcon contactIcon, QString contactString, QString contactUrl); public slots: void filterList(QString searchTerms); void updateAllContactsList(QString listType, QVariantList contactsVariantList, int totalReceivedCount); void addContact(ASPerson *contact); void removeContact(ASPerson *contact); void returnContact(); void returnClickedContact(QModelIndex modelIndex); private: PumpController *pController; QVBoxLayout *mainLayout; QLabel *searchLabel; QLineEdit *searchLineEdit; QListView *allContactsListView; QStandardItemModel *itemModel; QSortFilterProxyModel *filterModel; QPushButton *addToButton; QPushButton *cancelButton; }; #endif // PEOPLEWIDGET_H dianara-v1.3.2/src/peoplewidget.cpp000664 000764 000764 00000020246 12611533232 016663 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "peoplewidget.h" PeopleWidget::PeopleWidget(QString buttonText, int type, PumpController *pumpController, QWidget *parent) : QWidget(parent) { this->setMinimumSize(120, 120); this->pController = pumpController; connect(pController, SIGNAL(contactListReceived(QString,QVariantList,int)), this, SLOT(updateAllContactsList(QString,QVariantList,int))); connect(pController, SIGNAL(contactFollowed(ASPerson*)), this, SLOT(addContact(ASPerson*))); connect(pController, SIGNAL(contactUnfollowed(ASPerson*)), this, SLOT(removeContact(ASPerson*))); this->searchLabel = new QLabel(tr("&Search:")); this->searchLineEdit = new QLineEdit(); searchLineEdit->setPlaceholderText(tr("Enter a name here to search for it")); searchLabel->setBuddy(searchLineEdit); connect(searchLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterList(QString))); itemModel = new QStandardItemModel(this); filterModel = new QSortFilterProxyModel(this); filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); filterModel->setSourceModel(itemModel); allContactsListView = new QListView(); allContactsListView->setAlternatingRowColors(true); allContactsListView->setEditTriggers(QAbstractItemView::NoEditTriggers); allContactsListView->setDragDropMode(QListView::DragDrop); allContactsListView->setDefaultDropAction(Qt::CopyAction); allContactsListView->setModel(filterModel); connect(allContactsListView, SIGNAL(activated(QModelIndex)), this, SLOT(returnClickedContact(QModelIndex))); addToButton = new QPushButton(QIcon::fromTheme("list-add", QIcon(":/images/list-add.png")), buttonText); connect(addToButton, SIGNAL(clicked()), this, SLOT(returnContact())); // Layout mainLayout = new QVBoxLayout(); mainLayout->addWidget(searchLabel); mainLayout->addWidget(searchLineEdit); mainLayout->addSpacing(2); mainLayout->addWidget(allContactsListView); mainLayout->addSpacing(4); mainLayout->addWidget(addToButton); this->setLayout(mainLayout); if (type == EmbeddedWidget) { //allContactsListWidget->setSelectionMode(QListView::ExtendedSelection); // FIXME 1.3: For now, Single selection mode in both cases allContactsListView->setSelectionMode(QListView::SingleSelection); } else // StandaloneWidget { this->setWindowTitle(tr("Add a contact to a list") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("system-users")); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::WindowModal); allContactsListView->setSelectionMode(QListView::SingleSelection); cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("&Cancel")); connect(cancelButton, SIGNAL(clicked()), this, SLOT(hide())); mainLayout->addWidget(cancelButton); } qDebug() << "PeopleWidget created"; } PeopleWidget::~PeopleWidget() { qDebug() << "PeopleWidget destroyed"; } void PeopleWidget::resetWidget() { this->allContactsListView->scrollToTop(); this->searchLineEdit->clear(); // Might also trigger filterLists() this->searchLineEdit->setFocus(); } QStandardItem *PeopleWidget::createContactItem(ASPerson *contact) { QStandardItem *item; QString singleContactString = contact->getNameWithFallback() + " <" + contact->getId() + ">"; QString avatarFilename = MiscHelpers::getCachedAvatarFilename(contact->getAvatar()); QPixmap avatarPixmap = QPixmap(avatarFilename); if (!avatarPixmap.isNull()) { item = new QStandardItem(QIcon(avatarPixmap), singleContactString); } else { item = new QStandardItem(QIcon::fromTheme("user-identity", QIcon(":/images/no-avatar.png")), singleContactString); } item->setToolTip(contact->getTooltipInfo()); item->setData(contact->getUrl(), Qt::UserRole + 1); return item; } ////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// SLOTS ////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /* * Filter the list of all contacts based on what * the user entered in the search box * */ void PeopleWidget::filterList(QString searchTerms) { this->filterModel->setFilterFixedString(searchTerms); filterModel->sort(0); } /* * Update the list of all contacts (actually, 'following') * from the PumpController * */ void PeopleWidget::updateAllContactsList(QString listType, QVariantList contactsVariantList, int totalReceivedCount) { if (listType != "following") { return; } qDebug() << "PeopleWidget: received list of Following; updating..."; if (totalReceivedCount <= 200) // Only on first batch { this->itemModel->clear(); } foreach (QVariant contactVariant, contactsVariantList) { ASPerson *contact = new ASPerson(contactVariant.toMap(), this); QStandardItem *item = this->createContactItem(contact); itemModel->appendRow(item); delete contact; } filterModel->sort(0); // Sort by first column } void PeopleWidget::addContact(ASPerson *contact) { itemModel->appendRow(this->createContactItem(contact)); contact->deleteLater(); } void PeopleWidget::removeContact(ASPerson *contact) { foreach (QStandardItem *item, itemModel->findItems("<" + contact->getId() + ">", Qt::MatchEndsWith)) { itemModel->removeRow(item->row()); } contact->deleteLater(); } /* * Send current contact icon and string in a SIGNAL * * Used when selecting a row and clicking the "add" button * */ void PeopleWidget::returnContact() { if (this->allContactsListView->currentIndex().row() != -1) { QStandardItem *item = itemModel->itemFromIndex(filterModel->mapToSource(allContactsListView->currentIndex())); QIcon icon; if (item != 0) { icon = item->icon(); } emit addButtonPressed(icon, allContactsListView->currentIndex().data().toString(), // name allContactsListView->currentIndex().data(Qt::UserRole + 1).toString()); // URL } } /* * Used when clicking a contact * */ void PeopleWidget::returnClickedContact(QModelIndex modelIndex) { QStandardItem *item = itemModel->itemFromIndex(filterModel->mapToSource(modelIndex)); QIcon icon; if (item != 0) // Valid item { icon = item->icon(); } emit contactSelected(icon, modelIndex.data().toString(), modelIndex.data(Qt::UserRole + 1).toString()); } dianara-v1.3.2/src/logviewer.h000664 000764 000764 00000003361 12521026322 015637 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef LOGVIEWER_H #define LOGVIEWER_H #include #include #include #include #include #include #include #include #include #include #include #include #include "mischelpers.h" class LogViewer : public QWidget { Q_OBJECT public: explicit LogViewer(QWidget *parent = 0); ~LogViewer(); signals: public slots: void addToLog(QString message, QString url=""); void toggleVisibility(); protected: virtual void closeEvent(QCloseEvent *event); virtual void hideEvent(QHideEvent *event); virtual void showEvent(QShowEvent *event); private: QVBoxLayout *mainLayout; QTextBrowser *logTextBrowser; QHBoxLayout *buttonsLayout; QPushButton *clearButton; QPushButton *closeButton; QAction *closeAction; }; #endif // LOGVIEWER_H dianara-v1.3.2/src/logviewer.cpp000644 000764 000764 00000010212 12573333661 016177 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "logviewer.h" LogViewer::LogViewer(QWidget *parent) : QWidget(parent) { this->setWindowTitle(tr("Log") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("text-x-log", QIcon(":/images/log.png"))); this->setWindowFlags(Qt::Window); this->setMinimumSize(320, 400); QSettings settings; this->resize(settings.value("LogViewer/logWindowSize", QSize(760, 560)).toSize()); QList closeShortcuts; closeShortcuts << QKeySequence(Qt::Key_Escape); closeShortcuts << QKeySequence(Qt::Key_F12); closeAction = new QAction(this); closeAction->setShortcuts(closeShortcuts); connect(closeAction, SIGNAL(triggered()), this, SLOT(hide())); this->addAction(closeAction); logTextBrowser = new QTextBrowser(this); logTextBrowser->setReadOnly(true); logTextBrowser->setOpenExternalLinks(true); clearButton = new QPushButton(QIcon::fromTheme("edit-clear-list", QIcon(":/images/button-delete.png")), tr("Clear &Log"), this); connect(clearButton, SIGNAL(clicked()), logTextBrowser, SLOT(clear())); closeButton = new QPushButton(QIcon::fromTheme("window-close", QIcon(":/images/button-close.png")), tr("&Close"), this); connect(closeButton, SIGNAL(clicked()), this, SLOT(hide())); // Layout buttonsLayout = new QHBoxLayout(); buttonsLayout->addWidget(clearButton); buttonsLayout->addStretch(); buttonsLayout->addWidget(closeButton); mainLayout = new QVBoxLayout(); mainLayout->addWidget(logTextBrowser); mainLayout->addLayout(buttonsLayout); this->setLayout(mainLayout); qDebug() << "LogViewer created"; } LogViewer::~LogViewer() { qDebug() << "LogViewer destroyed"; } void LogViewer::closeEvent(QCloseEvent *event) { this->hide(); event->ignore(); } void LogViewer::hideEvent(QHideEvent *event) { QSettings settings; if (settings.isWritable()) { settings.setValue("LogViewer/logWindowSize", this->size()); } event->accept(); } /* * Scroll log to bottom when showing * */ void LogViewer::showEvent(QShowEvent *event) { this->logTextBrowser->moveCursor(QTextCursor::End); event->accept(); } /****************************************************************************/ /********************************* SLOTS ************************************/ /****************************************************************************/ void LogViewer::addToLog(QString message, QString url) { QString logLine = "[" + QDateTime::currentDateTime().toString(Qt::DefaultLocaleShortDate) + "] "; logLine.append(message); if (!url.isEmpty()) { logLine.append(": " + MiscHelpers::elidedText(url, 40) + ""); } logTextBrowser->append(logLine); } void LogViewer::toggleVisibility() { if (this->isVisible()) { this->hide(); } else { this->show(); } } dianara-v1.3.2/src/asperson.h000664 000764 000764 00000003552 12522272015 015473 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef ASPERSON_H #define ASPERSON_H #include #include #include class ASPerson : public QObject { Q_OBJECT public: explicit ASPerson(QVariantMap personMap, QObject *parent = 0); ~ASPerson(); void updateDataFromPerson(ASPerson *person); QString getId(); static QString cleanupId(QString originalId); QString getName(); QString getNameWithFallback(); QString getHometown(); QString getBio(); QString getAvatar(); QString getUrl(); QString getTooltipInfo(); QString getOutboxLink(); bool isFollowed(); //int getFollowingCount(); //int getFollowersCount(); QString getCreatedAt(); QString getupdatedAt(); signals: public slots: private: QString id; QString name; QString hometown; QString bio; QString avatar; QString url; QString outboxLink; QString followed; //int followingCount; //int followersCount; QString createdAt; QString updatedAt; }; #endif // ASPERSON_H dianara-v1.3.2/src/asperson.cpp000664 000764 000764 00000010276 12564147334 016042 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "asperson.h" ASPerson::ASPerson(QVariantMap personMap, QObject *parent) : QObject(parent) { this->id = this->cleanupId(personMap.value("id").toString()); this->name = personMap.value("displayName").toString().trimmed(); this->avatar = personMap.value("image").toMap() .value("url").toString(); this->url = personMap.value("url").toString(); this->hometown = personMap.value("location").toMap() .value("displayName").toString().trimmed(); this->bio = personMap.value("summary").toString(); this->outboxLink = personMap.value("links").toMap() .value("activity-outbox").toMap() .value("href").toString(); this->followed = personMap.value("pump_io").toMap() .value("followed").toString(); this->createdAt = personMap.value("published").toString(); this->updatedAt = personMap.value("updated").toString(); //qDebug() << "ASPerson created" << this->id; } ASPerson::~ASPerson() { //qDebug() << "ASPerson destroyed" << this->id; } void ASPerson::updateDataFromPerson(ASPerson *person) { this->id = person->getId(); this->name = person->getNameWithFallback(); this->avatar = person->getAvatar(); this->url = person->getUrl(); this->hometown = person->getHometown(); this->bio = person->getBio(); this->outboxLink = person->getOutboxLink(); this->followed = person->isFollowed() ? "true" : "false"; this->createdAt = person->getCreatedAt(); this->updatedAt = person->getupdatedAt(); } QString ASPerson::getId() { return id; } QString ASPerson::cleanupId(QString originalId) { if (originalId.startsWith("acct:")) { originalId.remove(0,5); } return originalId; } QString ASPerson::getName() { return name; } QString ASPerson::getNameWithFallback() { if (!name.isEmpty()) { return name; } else { return id; } } QString ASPerson::getHometown() { return hometown; } QString ASPerson::getBio() { return bio; } QString ASPerson::getAvatar() { return avatar; } QString ASPerson::getUrl() { return url; } QString ASPerson::getTooltipInfo() { if (this->id.isEmpty()) { return QString(); // If there's no ID, there's no valid person data } QString tooltipContents = "" + this->name + "
              "; tooltipContents.append("" + this->id + ""); if (!this->hometown.isEmpty() || !this->bio.isEmpty()) { // More data coming, add a line tooltipContents.append("

              "); } if (!this->hometown.isEmpty()) { tooltipContents.append("" + tr("Hometown") + ": " + hometown); tooltipContents.append("

              "); } if (!this->bio.isEmpty()) { tooltipContents.append(this->bio); } tooltipContents.replace("\n", "
              "); // HTML newlines return tooltipContents; } QString ASPerson::getOutboxLink() { return this->outboxLink; } bool ASPerson::isFollowed() { if (this->followed == "true") { return true; } else { return false; } } QString ASPerson::getCreatedAt() { return this->createdAt; } QString ASPerson::getupdatedAt() { return this->updatedAt; } dianara-v1.3.2/src/avatarbutton.h000664 000764 000764 00000004573 12522271554 016366 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef AVATARBUTTON_H #define AVATARBUTTON_H #include #include #include #include #include #include "asperson.h" #include "pumpcontroller.h" #include "globalobject.h" #include "mischelpers.h" class AvatarButton : public QToolButton { Q_OBJECT public: explicit AvatarButton(ASPerson *person, PumpController *pumpController, GlobalObject *globalObject, QSize avatarSize, QWidget *parent = 0); ~AvatarButton(); void setGenericAvatarIcon(); void updateAvatarIcon(QString filename); void createAvatarMenu(); void syncFollowState(bool firstTime=false); void setFollowUnfollow(); void addSeparatorToMenu(); void addActionToMenu(QAction *action); signals: public slots: void openAuthorProfileInBrowser(); void followUser(); void unfollowUser(); void sendMessageToUser(); void browseUserMessages(); void redrawAvatar(QString avatarUrl, QString avatarFilename); private: PumpController *pController; GlobalObject *globalObj; QMenu *avatarMenu; QAction *avatarMenuIdAction; QAction *avatarMenuProfileAction; QAction *avatarMenuFollowAction; QAction *avatarMenuMessageAction; QAction *avatarMenuBrowseAction; int iconWidth; QString authorId; QString authorName; QString authorUrl; QString authorAvatarUrl; QString authorOutbox; bool authorFollowed; }; #endif // AVATARBUTTON_H dianara-v1.3.2/src/avatarbutton.cpp000664 000764 000764 00000025252 12564160235 016715 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "avatarbutton.h" AvatarButton::AvatarButton(ASPerson *person, PumpController *pumpController, GlobalObject *globalObject, QSize avatarSize, QWidget *parent) : QToolButton(parent) { this->pController = pumpController; this->globalObj = globalObject; this->setPopupMode(QToolButton::InstantPopup); this->setStyleSheet("QToolButton { border: none; " " border-radius: 8px; " " padding: 2px }" "QToolButton:hover { border: none; " " background-color: " " palette(highlight) }"); this->setIconSize(avatarSize); this->setMinimumSize(avatarSize); this->iconWidth = avatarSize.width(); this->setToolTip(person->getTooltipInfo()); // Get local file name for avatar, which is stored in base64 hash form QString avatarFile = MiscHelpers::getCachedAvatarFilename(person->getAvatar()); if (QFile::exists(avatarFile)) { this->updateAvatarIcon(avatarFile); } else { this->setGenericAvatarIcon(); qDebug() << "AvatarButton() Using placeholder, downloading real avatar now"; pController->enqueueAvatarForDownload(person->getAvatar()); connect(pController, SIGNAL(avatarStored(QString,QString)), this, SLOT(redrawAvatar(QString,QString))); } this->authorId = person->getId(); this->authorName = person->getNameWithFallback(); this->authorUrl = person->getUrl(); this->authorAvatarUrl = person->getAvatar(); this->authorOutbox = person->getOutboxLink(); this->authorFollowed = false; // Real status will be read when creating the menu createAvatarMenu(); if (!authorId.isEmpty()) // Don't add the menu for invalid users { this->setMenu(avatarMenu); } qDebug() << "AvatarButton created"; } AvatarButton::~AvatarButton() { qDebug() << "AvatarButton destroyed"; } void AvatarButton::setGenericAvatarIcon() { this->setIcon(QIcon::fromTheme("user-identity", QIcon(":/images/no-avatar.png")) .pixmap(this->iconSize()) .scaledToWidth(this->iconWidth, Qt::SmoothTransformation)); } void AvatarButton::updateAvatarIcon(QString filename) { QPixmap avatarPixmap = QPixmap(filename) .scaledToWidth(this->iconWidth, Qt::SmoothTransformation); if (!avatarPixmap.isNull()) { this->setIcon(QIcon(avatarPixmap)); } else { qDebug() << "AvatarButton() avatar pixmap is null, using generic"; this->setGenericAvatarIcon(); } } /* * Create the menu shown when clicking the avatar * */ void AvatarButton::createAvatarMenu() { bool userIsAuthor = false; if (authorId == pController->currentUserId()) { userIsAuthor = true; // The post or comment is ours } this->avatarMenu = new QMenu(this); avatarMenu->setSeparatorsCollapsible(false); this->avatarMenuIdAction = new QAction(QIcon::fromTheme("user-identity", QIcon(":/images/no-avatar.png")), this->authorId, this); avatarMenuIdAction->setSeparator(true); // Make it nicer and not clickable avatarMenu->addAction(avatarMenuIdAction); QString openProfileString = tr("Open %1's profile in web browser") .arg(this->authorName); if (userIsAuthor) { openProfileString = tr("Open your profile in web browser"); } this->avatarMenuProfileAction = new QAction(QIcon::fromTheme("internet-web-browser", QIcon(":/images/no-avatar.png")), openProfileString, this); connect(avatarMenuProfileAction, SIGNAL(triggered()), this, SLOT(openAuthorProfileInBrowser())); if (authorUrl.isEmpty()) // Disable if there isn't actually an URL { avatarMenuProfileAction->setDisabled(true); } avatarMenu->addAction(avatarMenuProfileAction); this->avatarMenuFollowAction = new QAction("*follow/unfollow*", this); // Connections and proper label are set in setFollowUnfollow() this->avatarMenuMessageAction = new QAction(QIcon::fromTheme("document-edit", QIcon(":/images/button-edit.png")), tr("Send message to %1") .arg(this->authorName), this); connect(avatarMenuMessageAction, SIGNAL(triggered()), this, SLOT(sendMessageToUser())); this->avatarMenuBrowseAction = new QAction(QIcon::fromTheme("edit-find", QIcon(":/images/menu-find.png")), tr("Browse messages"), this); connect(avatarMenuBrowseAction, SIGNAL(triggered()), this, SLOT(browseUserMessages())); // Disable 'browse' option if not available (empty or in another server) if (!pController->urlIsInOurHost(this->authorOutbox)) { this->avatarMenuBrowseAction->setDisabled(true); } // Only add "follow/unfollow", "send message" and "browse messages" if (!userIsAuthor) // options if we're not the author { avatarMenu->addAction(avatarMenuFollowAction); this->syncFollowState(true); avatarMenu->addAction(avatarMenuMessageAction); avatarMenu->addAction(avatarMenuBrowseAction); } // // More options can be added from outside, via addActionToMenu() // } /* * See if we're currently following this user, according to the contact list * */ void AvatarButton::syncFollowState(bool firstTime) { bool authorFollowedBefore = this->authorFollowed; this->authorFollowed = this->pController->userInFollowing(authorId); if (this->authorFollowed != authorFollowedBefore || firstTime) { this->setFollowUnfollow(); } } /* * Set the icon and text of the follow/unfollow option of the avatar menu * according to whether we're following that user or not * */ void AvatarButton::setFollowUnfollow() { if (this->authorFollowed) { this->avatarMenuFollowAction->setIcon(QIcon::fromTheme("list-remove-user", QIcon(":/images/list-remove.png"))); this->avatarMenuFollowAction->setText(tr("Stop following")); connect(avatarMenuFollowAction, SIGNAL(triggered()), this, SLOT(unfollowUser())); disconnect(avatarMenuFollowAction, SIGNAL(triggered()), this, SLOT(followUser())); //qDebug() << "post author followed, connecting to UNFOLLOW()" << this->authorId; } else { this->avatarMenuFollowAction->setIcon(QIcon::fromTheme("list-add-user", QIcon(":/images/list-add.png"))); this->avatarMenuFollowAction->setText(tr("Follow")); connect(avatarMenuFollowAction, SIGNAL(triggered()), this, SLOT(followUser())); disconnect(avatarMenuFollowAction, SIGNAL(triggered()), this, SLOT(unfollowUser())); //qDebug() << "post author not followed, connecting to FOLLOW()" << this->authorId; } } void AvatarButton::addSeparatorToMenu() { avatarMenu->addSeparator(); } void AvatarButton::addActionToMenu(QAction *action) { avatarMenu->addAction(action); } //////////////////////////////////////////////////////////////////////////// ////////////////////////////////// SLOTS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////// void AvatarButton::openAuthorProfileInBrowser() { QDesktopServices::openUrl(this->authorUrl); } void AvatarButton::followUser() { this->pController->followContact(this->authorId); // Actual menu will be updated when appropriate SIGNAL is received } void AvatarButton::unfollowUser() { int confirmation = QMessageBox::question(this, tr("Stop following?"), tr("Are you sure you want to " "stop following %1?") .arg(this->authorId), tr("&Yes, stop following"), tr("&No"), "", 1, 1); if (confirmation == 0) { this->pController->unfollowContact(this->authorId); // Menu option will be updated when appropriate SIGNAL is received } } void AvatarButton::sendMessageToUser() { this->globalObj->createMessageForContact(this->authorId, this->authorName, this->authorUrl); } void AvatarButton::browseUserMessages() { this->globalObj->browseUserMessages(this->authorId, this->authorName, this->icon(), this->authorOutbox + "/major"); // TMP! } /* * Redraw avatar after receiving it * */ void AvatarButton::redrawAvatar(QString avatarUrl, QString avatarFilename) { if (avatarUrl == this->authorAvatarUrl) { this->updateAvatarIcon(avatarFilename); disconnect(pController, SIGNAL(avatarStored(QString,QString)), this, SLOT(redrawAvatar(QString,QString))); } } dianara-v1.3.2/src/colorpicker.h000664 000764 000764 00000003067 12451104247 016160 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef COLORPICKER_H #define COLORPICKER_H #include #include #include #include #include #include #include class ColorPicker : public QWidget { Q_OBJECT public: explicit ColorPicker(QString description, QString initialColorString, QWidget *parent = 0); ~ColorPicker(); void setButtonColor(QColor color); QString getCurrentColor(); signals: public slots: void changeColor(); private: QHBoxLayout *layout; QLabel *descriptionLabel; QCheckBox *checkBox; QPixmap buttonPixmap; QPushButton *button; QColor currentColor; }; #endif // COLORPICKER_H dianara-v1.3.2/src/colorpicker.cpp000644 000764 000764 00000006477 12601554056 016525 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "colorpicker.h" ColorPicker::ColorPicker(QString description, QString initialColorString, QWidget *parent) : QWidget(parent) { descriptionLabel = new QLabel(description, this); descriptionLabel->setWordWrap(true); descriptionLabel->setAlignment(Qt::AlignTop); checkBox = new QCheckBox(this); buttonPixmap = QPixmap(32, 32); button = new QPushButton(tr("Change"), this); button->setIconSize(QSize(32, 32)); button->setDisabled(true); // Disabled initially connect(button, SIGNAL(clicked()), this, SLOT(changeColor())); connect(checkBox, SIGNAL(toggled(bool)), button, SLOT(setEnabled(bool))); layout = new QHBoxLayout(); layout->addWidget(descriptionLabel, 10); layout->addSpacing(4); layout->addStretch(1); layout->addWidget(checkBox, 0); layout->addSpacing(8); layout->addWidget(button, 0); this->setLayout(layout); QColor initialColor(initialColorString); if (initialColor.isValid()) { this->currentColor = initialColor; this->checkBox->setChecked(true); } else { if (initialColorString.startsWith("DISABLED")) { this->currentColor = initialColorString.remove("DISABLED"); } else { this->currentColor = QColor(Qt::gray); } } this->setButtonColor(currentColor); qDebug() << "ColorPicker created"; } ColorPicker::~ColorPicker() { qDebug() << "ColorPicker destroyed"; } void ColorPicker::setButtonColor(QColor color) { buttonPixmap.fill(color); button->setIcon(QIcon(buttonPixmap)); } QString ColorPicker::getCurrentColor() { if (checkBox->isChecked()) { return this->currentColor.name(); // return in #RRGGBB format } else { return QString("DISABLED%1").arg(currentColor.name()); // Return invalid color } } //////////////////////////////////////////////////////////////////////////// ////////////////////////////////// SLOTS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////// void ColorPicker::changeColor() { QColor newColor = QColorDialog::getColor(currentColor, this, tr("Choose a color")); if (newColor.isValid()) { currentColor = newColor; setButtonColor(currentColor); } qDebug() << "New color:" << currentColor; } dianara-v1.3.2/src/filterchecker.h000664 000764 000764 00000003021 12451104251 016437 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef FILTERCHECKER_H #define FILTERCHECKER_H #include #include #include #include "asactivity.h" class FilterChecker : public QObject { Q_OBJECT public: explicit FilterChecker(QObject *parent = 0); ~FilterChecker(); void setFilters(QVariantList newFiltersList); int validateActivity(ASActivity *activity); enum FilterTypes { FilterOut, Highlight, NoFiltering = 999 }; signals: public slots: private: QList filteredContent; QList filteredAuthor; QList filteredGenerator; QList filteredDescription; }; #endif // FILTERCHECKER_H dianara-v1.3.2/src/filterchecker.cpp000664 000764 000764 00000010360 12451104250 016775 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "filterchecker.h" FilterChecker::FilterChecker(QObject *parent) : QObject(parent) { qDebug() << "FilterChecker created"; } FilterChecker::~FilterChecker() { qDebug() << "FilterChecker destroyed"; } /* * Set or update filter rules; called from the FilterEditor * */ void FilterChecker::setFilters(QVariantList newFiltersList) { filteredContent.clear(); filteredAuthor.clear(); filteredGenerator.clear(); filteredDescription.clear(); // Define new filter values foreach (QVariant filter, newFiltersList) { QString actionType = filter.toMap().value("action").toString(); int filterType = filter.toMap().value("type").toInt(); QString filterText = filter.toMap().value("text").toString(); QStringList pair; pair << filterText << actionType; switch (filterType) { case 0: // post content filteredContent.append(pair); break; case 1: // author filteredAuthor.append(pair); break; case 2: // application (generator) filteredGenerator.append(pair); break; case 3: // activity description filteredDescription.append(pair); break; default: break; } } qDebug() << "FilterChecker() filters updated, with" << newFiltersList.length() << "filters"; } /* * Return NoFiltering, FilterOut or Highlight * */ int FilterChecker::validateActivity(ASActivity *activity) { int filtered = NoFiltering; // Innocent until proven guilty! QString postTitle = activity->object()->getTitle(); QString postContents = activity->object()->getContent(); foreach (QStringList contents, filteredContent) { if (postContents.contains(contents.first(), Qt::CaseInsensitive) || postTitle.contains(contents.first(), Qt::CaseInsensitive)) { qDebug() << "Filtering item because of Post Content:" << contents.first(); filtered = contents.last().toInt(); } } QString activityAuthorId = activity->author()->getId(); QString objectAuthorId = activity->object()->author()->getId(); foreach (QStringList authorId, filteredAuthor) { if (activityAuthorId.contains(authorId.first(), Qt::CaseInsensitive) || objectAuthorId.contains(authorId.first(), Qt::CaseInsensitive)) { qDebug() << "Filtering item because of Author ID:" << authorId.first(); filtered = authorId.last().toInt(); } } QString activityGenerator = activity->getGenerator(); foreach (QStringList generator, filteredGenerator) { if (activityGenerator.contains(generator.first(), Qt::CaseInsensitive)) { qDebug() << "Filtering item because of Application (generator):" << generator.first(); filtered = generator.last().toInt(); } } // Remove links from activity description, so it's easier to write filtering rules QString activityDescription = MiscHelpers::htmlWithoutLinks(activity->getContent()); foreach (QStringList description, filteredDescription) { if (activityDescription.contains(description.first(), Qt::CaseInsensitive)) { qDebug() << "Filtering item because of Activity Description:" << description.first(); filtered = description.last().toInt(); } } return filtered; } dianara-v1.3.2/src/downloadwidget.cpp000664 000764 000764 00000015406 12611531370 017211 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "downloadwidget.h" DownloadWidget::DownloadWidget(QString fileUrl, QString suggestedFN, PumpController *pumpController, QWidget *parent) : QFrame(parent) { this->pController = pumpController; this->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); this->downloading = false; this->fileUrl = fileUrl; this->suggestedFilename = suggestedFN; QFont infoFont; infoFont.setPointSize(infoFont.pointSize() - 1); infoLabel = new QLabel(this); infoLabel->setAlignment(Qt::AlignCenter); infoLabel->setFont(infoFont); infoLabel->hide(); downloadButton = new QPushButton(QIcon::fromTheme("download", QIcon(":/images/button-download.png")), tr("Download"), this); downloadButton->setToolTip("" + tr("Save the attached file to your folders")); downloadButton->setFlat(true); connect(downloadButton, SIGNAL(clicked()), this, SLOT(downloadAttachment())); progressBar = new QProgressBar(this); progressBar->setValue(0); progressBar->hide(); // Initially hidden cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("Cancel"), this); cancelButton->hide(); connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelDownload())); mainLayout = new QHBoxLayout(); mainLayout->addWidget(infoLabel); mainLayout->addWidget(downloadButton); mainLayout->addWidget(progressBar); mainLayout->addWidget(cancelButton); this->setLayout(mainLayout); qDebug() << "DownloadWidget created"; } DownloadWidget::~DownloadWidget() { qDebug() << "DownloadWidget destroyed"; } void DownloadWidget::resetWidget() { this->downloadButton->show(); this->progressBar->hide(); this->cancelButton->hide(); networkReply->disconnect(); networkReply->deleteLater(); disconnect(pController, SIGNAL(downloadCompleted(QString)), this, SLOT(completeDownload(QString))); disconnect(pController, SIGNAL(downloadFailed(QString)), this, SLOT(onDownloadFailed(QString))); downloadedFile.close(); this->downloading = false; } //////////////////////////////////////////////////////////////////////////// ////////////////////////////////// SLOTS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////// void DownloadWidget::downloadAttachment() { if (downloading) { qDebug() << "Already downloading!!"; return; } QString filename; filename = QFileDialog::getSaveFileName(this, tr("Save File As..."), QDir::homePath() + "/" + suggestedFilename, tr("All files") + " (*)"); if (!filename.isEmpty()) { this->downloading = true; this->infoLabel->show(); this->downloadButton->hide(); this->progressBar->show(); this->progressBar->setValue(0); this->progressBar->setToolTip(""); this->cancelButton->show(); this->downloadedFile.setFileName(filename); downloadedFile.open(QIODevice::WriteOnly); networkReply = this->pController->getMedia(fileUrl); connect(networkReply, SIGNAL(readyRead()), this, SLOT(storeFileData())); connect(networkReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateProgressBar(qint64,qint64))); connect(pController, SIGNAL(downloadCompleted(QString)), this, SLOT(completeDownload(QString))); connect(pController, SIGNAL(downloadFailed(QString)), this, SLOT(onDownloadFailed(QString))); } } void DownloadWidget::cancelDownload() { int confirmation = QMessageBox::question(this, tr("Abort download?"), tr("Do you want to stop " "downloading the attached " "file?"), tr("&Yes, stop"), tr("&No, continue"), "", 1, 1); if (confirmation == 0) { this->networkReply->abort(); infoLabel->setText(tr("Download aborted")); resetWidget(); } else { qDebug() << "Confirmation cancelled, NOT stopping download"; } } void DownloadWidget::completeDownload(QString url) { if (url == this->fileUrl) // Ensure completed download is this download { infoLabel->setText(tr("Download completed")); resetWidget(); } } void DownloadWidget::onDownloadFailed(QString url) { if (url == this->fileUrl) // Ensure failed download is this download { infoLabel->setText(tr("Download failed")); qDebug() << "Download FAILED!" << url; resetWidget(); } } void DownloadWidget::storeFileData() { QByteArray data = networkReply->readAll(); this->downloadedFile.write(data); } void DownloadWidget::updateProgressBar(qint64 received, qint64 total) { this->progressBar->setRange(0, total); this->progressBar->setValue(received); this->infoLabel->setText(tr("Downloading %1 KiB...") .arg(QLocale::system().toString(total / 1024))); QString downloadedTooltip = tr("%1 KiB downloaded") .arg(QLocale::system().toString(received / 1024)); this->infoLabel->setToolTip(downloadedTooltip); this->progressBar->setToolTip(downloadedTooltip); } dianara-v1.3.2/src/contactlist.cpp000644 000764 000764 00000026334 12601532111 016516 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "contactlist.h" ContactList::ContactList(PumpController *pumpController, GlobalObject *globalObject, QString listType, QWidget *parent) : QWidget(parent) { this->pController = pumpController; this->globalObj = globalObject; QString filterNote = tr("Type a partial name or ID to find a contact...") + " (Control+F)"; this->filterLineEdit = new QLineEdit(this); filterLineEdit->setPlaceholderText(filterNote); filterLineEdit->setToolTip("" + filterNote); // HTMLized for wordwrapping connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterList(QString))); int iconSize = this->filterLineEdit->sizeHint().height(); // filterLineEdit->font().pixelSize()? this->filterIcon = new QLabel(this); filterIcon->setAlignment(Qt::AlignCenter); filterIcon->setPixmap(QIcon::fromTheme("edit-find", QIcon(":/images/menu-find.png")) .pixmap(iconSize, iconSize)); this->removeFilterButton = new QPushButton(QIcon::fromTheme("view-list-icons"), tr("F&ull List"), this); removeFilterButton->setDisabled(true); // Disabled initially, until a search happens connect(removeFilterButton, SIGNAL(clicked()), filterLineEdit, SLOT(clear())); goToFilterAction = new QAction(this); goToFilterAction->setShortcut(QKeySequence("Ctrl+F")); connect(goToFilterAction, SIGNAL(triggered()), filterLineEdit, SLOT(setFocus())); this->addAction(goToFilterAction); // Layout this->filterLayout = new QHBoxLayout(); filterLayout->addWidget(filterIcon, 0, Qt::AlignVCenter); filterLayout->addWidget(filterLineEdit); filterLayout->addWidget(removeFilterButton); this->contactsLayout = new QVBoxLayout(); this->contactsWidget = new QWidget(this); contactsWidget->setLayout(contactsLayout); this->contactsScrollArea = new QScrollArea(this); contactsScrollArea->setWidget(contactsWidget); contactsScrollArea->setWidgetResizable(true); contactsScrollArea->setFrameStyle(QFrame::NoFrame); contactsScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); this->mainLayout = new QVBoxLayout(); mainLayout->addWidget(contactsScrollArea); mainLayout->addLayout(filterLayout); this->setLayout(mainLayout); // Add demo contacts QVariantMap demoContactData; QVariantMap demoContactHometown; QVariantMap demoContactFollowed; this->isFollowing = false; if (listType == "following") { isFollowing = true; demoContactData.insert("displayName", "Demo Contact"); demoContactData.insert("id", "democontact@pumpserver.org"); demoContactData.insert("url", "https://jancoding.wordpress.com"); demoContactFollowed.insert("followed", "true"); } else { demoContactData.insert("displayName", "Demo Follower"); demoContactData.insert("id", "demofollower@pumpserver.org"); demoContactData.insert("url", "http://dianara.nongnu.org"); demoContactFollowed.insert("followed", "false"); } demoContactHometown.insert("displayName", "Some city"); demoContactData.insert("location", demoContactHometown); demoContactData.insert("pump_io", demoContactFollowed); demoContactData.insert("published", "2013-05-01T00:00:00Z"); // Dianara's birthday ASPerson *demoContactPerson = new ASPerson(demoContactData, this); ContactCard *demoContactCard = new ContactCard(this->pController, this->globalObj, demoContactPerson, this); this->contactsLayout->addWidget(demoContactCard); this->contactsInList.append(demoContactCard); qDebug() << "ContactList created"; } ContactList::~ContactList() { qDebug() << "ContactList destroyed"; } void ContactList::clearListContents() { foreach (ContactCard *card, contactsInList) { this->contactsLayout->removeWidget(card); delete card; } this->contactsInList.clear(); this->contactsStringForExport.clear(); if (this->isFollowing) { this->globalObj->clearNickCompletionModel(); } } void ContactList::setListContents(QVariantList contactList) { qDebug() << "ContactList; Setting list contents"; QString contactInfoLineString; QStringList followingIdStringList; foreach (QVariant contact, contactList) { QVariantMap contactMap = contact.toMap(); if (!contactMap.keys().contains("id")) { /* * Temporary hack to fix user profiles when the list comes * from the site user list, which is currently empty, * and contains ID only inside the profile property * */ QString id = ASPerson::cleanupId(contactMap.value("profile").toMap() .value("id").toString()); contactMap.insert("id", id); QVariantMap followedMap; followedMap.insert("followed", pController->userInFollowing(id)); contactMap.insert("pump_io", followedMap); id.remove("@" + pController->currentServerUrl()); contactMap.insert("displayName", id); contactMap.insert("url", pController->currentServerScheme() + pController->currentServerUrl() + "/" + id); QVariantMap hrefMap; hrefMap.insert("href", pController->currentServerScheme() + pController->currentServerUrl() + "/api/user/" + id + "/feed"); QVariantMap outboxMap; outboxMap.insert("activity-outbox", hrefMap); contactMap.insert("links", outboxMap); } ASPerson *person = new ASPerson(contactMap, this); ContactCard *contactCard = new ContactCard(this->pController, this->globalObj, person, this); this->contactsLayout->addWidget(contactCard); this->contactsInList.append(contactCard); // Info for the string list used when exporting contactInfoLineString = person->getName() + " <" + person->getId() + ">\n"; contactsStringForExport.append(contactInfoLineString); if (this->isFollowing) { // Add to internal following list for PumpController followingIdStringList.append(person->getId()); // Add also to GlobalObject's model for nick completion this->globalObj->addToNickCompletionModel(person->getId(), person->getNameWithFallback(), person->getUrl()); } } // end foreach // Batch of contacts added to list, add them also to the internal list if (this->isFollowing) { this->pController->updateInternalFollowingIdList(followingIdStringList); this->globalObj->sortNickCompletionModel(); // FIXME: should only sort when the whole list is done } this->filterLineEdit->clear(); // Unfilter the list } QString ContactList::getContactsStringForExport() { return this->contactsStringForExport; } /*****************************************************************************/ /*********************************** SLOTS ***********************************/ /*****************************************************************************/ void ContactList::filterList(QString filterText) { qDebug() << "Filtering for contacts matching:" << filterText; if (!filterText.isEmpty()) { foreach (ContactCard *card, contactsInList) { if (card->getNameAndIdString().contains(filterText, Qt::CaseInsensitive)) { card->show(); } else { card->hide(); } } removeFilterButton->setEnabled(true); } else // If no filter at all, more optimized version showing all { foreach (ContactCard *card, contactsInList) { card->show(); } removeFilterButton->setDisabled(true); } } void ContactList::addSingleContact(ASPerson *contact) { ContactCard *card = new ContactCard(this->pController, this->globalObj, contact, this); this->contactsLayout->insertWidget(0, card); this->contactsInList.append(card); emit contactCountChanged(1); // This check is actually unnecessary, since this slot is only called if (this->isFollowing) // for the 'following' list { QStringList contactsToAdd; contactsToAdd.append(contact->getId()); this->pController->updateInternalFollowingIdList(contactsToAdd); // Add also to GlobalObject's model for nick completion this->globalObj->addToNickCompletionModel(contact->getId(), contact->getNameWithFallback(), contact->getUrl()); this->globalObj->sortNickCompletionModel(); } } void ContactList::removeSingleContact(ASPerson *contact) { foreach (ContactCard *card, contactsInList) { /* Ignore disabled cards, to avoid substracting more than once, in case * there were more ContactCards for the same contact, from following * and unfollowing previously. * */ if (card->isEnabled() && card->getId() == contact->getId()) { emit contactCountChanged(-1); this->pController->removeFromInternalFollowingList(contact->getId()); // Remove from GlobalObject's model for nick completion, too this->globalObj->removeFromNickCompletionModel(contact->getId()); card->setDisabled(true); } } contact->deleteLater(); } dianara-v1.3.2/src/downloadwidget.h000664 000764 000764 00000003754 12451104246 016661 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef DOWNLOADWIDGET_H #define DOWNLOADWIDGET_H #include #include #include #include #include #include #include #include #include "pumpcontroller.h" class DownloadWidget : public QFrame { Q_OBJECT public: explicit DownloadWidget(QString fileUrl, QString suggestedFN, PumpController *pumpController, QWidget *parent = 0); ~DownloadWidget(); void resetWidget(); signals: public slots: void downloadAttachment(); void cancelDownload(); void completeDownload(QString url); void onDownloadFailed(QString url); void storeFileData(); void updateProgressBar(qint64 received, qint64 total); private: QHBoxLayout *mainLayout; QLabel *infoLabel; QProgressBar *progressBar; QPushButton *downloadButton; QPushButton *cancelButton; QString fileUrl; QString suggestedFilename; PumpController *pController; QNetworkReply *networkReply; QFile downloadedFile; bool downloading; }; #endif // DOWNLOADWIDGET_H dianara-v1.3.2/src/proxydialog.cpp000664 000764 000764 00000013772 12451104251 016537 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "proxydialog.h" ProxyDialog::ProxyDialog(int proxyType, QString hostname, QString port, bool useAuth, QString user, QString password, QWidget *parent) : QWidget(parent) { this->setWindowTitle(tr("Proxy Configuration") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("preferences-system-network", QIcon(":/images/button-configure.png"))); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::WindowModal); this->setMinimumSize(380, 320); proxyTypeComboBox = new QComboBox(); proxyTypeComboBox->addItem(tr("Do not use a proxy")); proxyTypeComboBox->addItem("SOCKS 5"); proxyTypeComboBox->addItem("HTTP"); proxyTypeComboBox->setCurrentIndex(proxyType); hostnameLineEdit = new QLineEdit(hostname); hostnameLineEdit->setPlaceholderText("example.org"); portLineEdit = new QLineEdit(port); portLineEdit->setPlaceholderText("1080, 8080..."); // defaults for socks5 and http authCheckBox = new QCheckBox(); authCheckBox->setChecked(useAuth); connect(authCheckBox, SIGNAL(toggled(bool)), this, SLOT(toggleAuth(bool))); userLineEdit = new QLineEdit(user); userLineEdit->setPlaceholderText(tr("Your proxy username")); passwordLineEdit = new QLineEdit(password); passwordLineEdit->setEchoMode(QLineEdit::Password); passwordNoteLabel = new QLabel(tr("Note: Password is not stored in a " "secure manner. If you wish, you can " "leave the field empty, and you'll be " "prompted for the password on startup.")); passwordNoteLabel->setWordWrap(true); QFont noteFont; noteFont.setPointSize(noteFont.pointSize() - 1); passwordNoteLabel->setFont(noteFont); this->toggleAuth(useAuth); // Enable or disable initially // Bottom saveButton = new QPushButton(QIcon::fromTheme("document-save", QIcon(":/images/button-save.png")), tr("&Save")); connect(saveButton, SIGNAL(clicked()), this, SLOT(saveSettings())); cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("&Cancel")); connect(cancelButton, SIGNAL(clicked()), this, SLOT(hide())); // ESC to close closeAction = new QAction(this); closeAction->setShortcut(QKeySequence(Qt::Key_Escape)); connect(closeAction, SIGNAL(triggered()), this, SLOT(hide())); this->addAction(closeAction); //////////////////////////////////////////// Layout fieldsLayout = new QFormLayout(); fieldsLayout->addRow(tr("Proxy &Type"), proxyTypeComboBox); fieldsLayout->addRow(tr("&Hostname"), hostnameLineEdit); fieldsLayout->addRow(tr("&Port"), portLineEdit); fieldsLayout->addRow(tr("Use &Authentication"), authCheckBox); fieldsLayout->addRow(tr("&User"), userLineEdit); fieldsLayout->addRow(tr("Pass&word"), passwordLineEdit); fieldsLayout->addRow("", passwordNoteLabel); buttonsLayout = new QHBoxLayout(); buttonsLayout->setAlignment(Qt::AlignRight); buttonsLayout->addWidget(saveButton); buttonsLayout->addWidget(cancelButton); mainLayout = new QVBoxLayout(); mainLayout->addLayout(fieldsLayout); mainLayout->addSpacing(16); mainLayout->addStretch(1); mainLayout->addLayout(buttonsLayout); this->setLayout(mainLayout); qDebug() << "ProxyDialog created"; } ProxyDialog::~ProxyDialog() { qDebug() << "ProxyDialog destroyed"; } ////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// SLOTS ////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void ProxyDialog::toggleAuth(bool state) { this->userLineEdit->setEnabled(state); this->passwordLineEdit->setEnabled(state); } void ProxyDialog::saveSettings() { QSettings settings; settings.beginGroup("Configuration"); settings.setValue("proxyType", this->proxyTypeComboBox->currentIndex()); settings.setValue("proxyHostname", this->hostnameLineEdit->text()); settings.setValue("proxyPort", this->portLineEdit->text()); settings.setValue("proxyUseAuth", this->authCheckBox->isChecked()); if (!authCheckBox->isChecked()) // If no auth, clear saved username/passwd { userLineEdit->clear(); passwordLineEdit->clear(); } settings.setValue("proxyUser", this->userLineEdit->text()); // VERY TMP: Saving passwd as base64 for now; FIXME settings.setValue("proxyPassword", this->passwordLineEdit->text().toUtf8().toBase64()); settings.endGroup(); this->hide(); qDebug() << "ProxyDialog::saveSettings()" << hostnameLineEdit->text() << portLineEdit->text(); } dianara-v1.3.2/src/proxydialog.h000664 000764 000764 00000003710 12451104251 016173 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef PROXYDIALOG_H #define PROXYDIALOG_H #include #include #include #include #include #include #include #include #include #include #include #include class ProxyDialog : public QWidget { Q_OBJECT public: explicit ProxyDialog(int proxyType, QString hostname, QString port, bool useAuth, QString user, QString password, QWidget *parent = 0); ~ProxyDialog(); signals: public slots: void toggleAuth(bool state); void saveSettings(); private: QVBoxLayout *mainLayout; QFormLayout *fieldsLayout; QHBoxLayout *buttonsLayout; QComboBox *proxyTypeComboBox; QLineEdit *hostnameLineEdit; QLineEdit *portLineEdit; QCheckBox *authCheckBox; QLineEdit *userLineEdit; QLineEdit *passwordLineEdit; QLabel *passwordNoteLabel; QPushButton *saveButton; QPushButton *cancelButton; QAction *closeAction; }; #endif // PROXYDIALOG_H dianara-v1.3.2/src/helpwidget.cpp000644 000764 000764 00000041016 12613256554 016336 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "helpwidget.h" HelpWidget::HelpWidget(QWidget *parent) : QWidget(parent) { this->setWindowTitle(tr("Basic Help") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("system-help")); this->setWindowFlags(Qt::Window); this->setMinimumSize(320, 240); QSettings settings; this->resize(settings.value("HelpWidget/helpWindowSize", QSize(580, 700)).toSize()); ///////////////////////////////////////////////////////////////// Help text QString helpText; // Table of contents const QString sectionStarting = tr("Getting started"); const QString sectionSettings = tr("Settings"); const QString sectionTimelines = tr("Timelines"); const QString sectionPosting = tr("Posting"); const QString sectionContacts = tr("Managing contacts"); const QString sectionKeyboard = tr("Keyboard controls"); const QString sectionCommandLine = tr("Command line options"); helpText.append("

              " + tr("Contents") + "

              " "" "
              "); // Actual contents helpText.append("

              " + sectionStarting + "

              "); helpText.append(tr("The first time you start Dianara, you should see " "the Account Configuration dialog. There, enter " "your Pump.io address as name@server and press " "the Get Verifier Code button.") + "

              "); helpText.append(tr("Then, your usual web browser should load the authorization " "page in your Pump.io server. There, you'll have to copy " "the full VERIFIER code, and paste it into Dianara's second field. " "Then press Authorize Application, and once it's confirmed, press " "Save Details.") + "

              "); helpText.append(tr("At this point, your profile, contact lists and timelines " "will be loaded.") + "

              "); helpText.append(tr("You should take a look at the Program Configuration window, " "under the Settings - Configure Dianara menu. There are " "several interesting options there.") + "

              "); helpText.append(tr("Keep in mind that there are a lot of places in Dianara " "where you can get more information by hovering over some " "text or button with your mouse, and waiting for the " "tooltip to appear.") + "

              "); helpText.append(tr("If you're new to Pump.io, take a look at this guide:") + " " + tr("Pump.io User Guide") + ""); helpText.append("

              "); helpText.append("

              " + sectionSettings + "

              "); helpText.append(tr("You can configure several things to your liking in the " "settings, like the time interval between timeline " "updates, how many posts per page you want, highlight " "colors, notifications or how the system tray icon looks.") + "

              "); helpText.append(tr("Here, you can also activate the option to always publish " "your posts as Public by default. You can always change " "that at the moment of posting.")); helpText.append("

              "); helpText.append("

              " + sectionTimelines + "

              "); helpText.append(tr("There are seven timelines:") + "
                " + "
              • " + tr("The main timeline, where you'll see all the stuff " "posted or shared by the people you follow.") + "
              • " + "
              • " + tr("Messages timeline, where you'll see messages sent " "to you specifically. These messages might have been " "sent to other people too.") + "
              • " + "
              • " + tr("Activity timeline, where you'll see your own posts, " "or posts shared by you.") + "
              • " + "
              • " + tr("Favorites timeline, where you'll see the posts and " "comments you've liked. This can be used as a " "bookmark system.") + "
              • " + "
              " + "
              "); helpText.append(tr("The fifth timeline is the minor timeline, also known " "as the Meanwhile. This is visible on the left side, " "though it can be hidden. Here you'll see minor activities " "done by everyone you follow, such as comment actions, " "liking posts or following people.", "LEFT SIDE should change to RIGHT SIDE on RTL languages") + "
              " + tr("The sixth and seventh timelines are also minor " "timelines, similar to the Meanwhile, but containing " "only activities directly addressed to you (Mentions) " "and activities done by you (Actions).") + "

              " + tr("These activities might have a '+' button in them. " "Press it to open the post they're referencing. " "Also, as in many other places, you can hover with " "your mouse to see relevant information in the tooltip.") + "

              " + tr("New messages appear highlighted in a different color. " "You can mark them as read just by clicking on any " "empty parts of the message.")); helpText.append("

              "); helpText.append("

              " + sectionPosting + "

              "); helpText.append(tr("You can post notes by clicking in the text field at " "the top of the window or by pressing Control+N. " "Setting a title for your post is optional, but " "highly recommended, as it will help to " "better identify references to your post in " "the minor feed, e-mail notifications, etc.") + "

              "); helpText.append(tr("It is possible to attach images, audio, video, and " "general files, like PDF documents, to your post.") + "
              "); helpText.append(tr("You can use the Format button to add formatting to " "your text, like bold or italics. Some of these options " "require text to be selected before they are used.") + "

              "); helpText.append(tr("You can select who will see your post by using the " "To and Cc buttons.") + " " + tr("If you add a specific person to the 'To' list, they " "will receive your message in their direct messages tab.") + "
              "); helpText.append(tr("You can also type '@' and the first characters of the " "name of a contact to bring up a popup menu with " "matching choices.") + " " + tr("Choose one with the arrow keys and press Enter to " "complete the name. This will add that person to the " "recipients list.") + "

              "); helpText.append(tr("You can create private messages by adding specific " "people to these lists, and unselecting the Followers " "or the Public options.")); helpText.append("

              "); helpText.append("

              " + sectionContacts + "

              "); helpText.append(tr("You can see the lists of people you follow, and who " "follow you from the Contacts tab.") + " " + tr("There, you can also manage person lists, used mainly " "to send posts to specific groups of people.") + " " + tr("There is a text field at the top, where you can " "directly enter addresses of new contacts to " "follow them.") + "

              " + tr("Under the 'Neighbors' tab you'll see some resources " "to find people, and have the option to browse the " "latest registered users from your server directly.") + "


              "); helpText.append(tr("You can find a list with some Pump.io users " "and other information here:") + "
              " "" "static.jpope.org/users.html - " "" + tr("Users by language") + ""); helpText.append("

              "); helpText.append("

              " + sectionKeyboard + "

              "); helpText.append(tr("The most common actions found on the menus have " "keyboard shortcuts written next to them, like F5 " "or Control+N.") + "

              " + tr("Besides that, you can use:") + "
                " + "
              • " + tr("Control+Up/Down/PgUp/PgDown/Home/End to move " "around the timeline.") + "
              • " + "
              • " + tr("Control+Left/Right to jump one page in the " "timeline.") + "
              • " + "
              • " + tr("Control+G to go to any page in the timeline " "directly.") + "
              • " + "
              • " + tr("Control+1/2/3 to switch between the minor " "feeds.") + "
              • " + "
              • " + tr("Control+Enter to post, when you're done composing " "a note or a comment. If the note is empty, you can " "cancel it by pressing ESC.") + "
              • " + "
              • " + tr("While composing a note, press Enter to jump from the " "title to the message body. Also, pressing the Up " "arrow while you're at the start of the message, jumps " "back to the title.") + "
              • " + "
              • " + tr("Control+Enter to finish creating a list of recipients " "for a post, in the 'To' or 'Cc' lists.") + "
              • " + "
              "); helpText.append("

              "); helpText.append("

              " + sectionCommandLine + "

              "); helpText.append(tr("You can use the --config parameter to run the program " "with a different configuration. This can be useful to " "use two or more different accounts. You can even run two " "instances of Dianara at the same time.") + "

              "); helpText.append(tr("Use the --debug parameter to have extra information " "in your terminal window, about what the program is doing.") + "

              "); helpText.append(tr("If your server does not support HTTPS, you can use the " "--nohttps parameter.") + "


              "); helpText.append(tr("Dianara offers a D-Bus interface that allows some " "control from other applications.") + " " + tr("The interface is at %1, and you can " "access it with tools such as %2 or %3. It " "offers methods like %4 and %5.") .arg("org.nongnu.dianara") .arg("qdbus").arg("dbus-send") .arg("'toggle'").arg("'post'") + "
              " + tr("If you use an alternate configuration, with " "something like '--config otherconf', then the " "interface will be at org.nongnu.dianara_otherconf.")); helpText.append("

              "); ///////////////////////////////////////////////////////////////// Help text helpTextBrowser = new QTextBrowser(this); helpTextBrowser->setReadOnly(true); helpTextBrowser->setOpenExternalLinks(true); helpTextBrowser->setText(helpText); closeButton = new QPushButton(QIcon::fromTheme("window-close", QIcon(":/images/button-close.png")), tr("&Close"), this); connect(closeButton, SIGNAL(clicked()), this, SLOT(hide())); QList closeShortcuts; closeShortcuts << QKeySequence(Qt::Key_Escape); closeShortcuts << QKeySequence(Qt::Key_F1); closeAction = new QAction(this); closeAction->setShortcuts(closeShortcuts); connect(closeAction, SIGNAL(triggered()), this, SLOT(hide())); this->addAction(closeAction); mainLayout = new QVBoxLayout(); mainLayout->setContentsMargins(2, 2, 2, 2); mainLayout->addWidget(helpTextBrowser); mainLayout->addWidget(closeButton, 0, Qt::AlignRight); this->setLayout(mainLayout); qDebug() << "HelpWidget created"; } HelpWidget::~HelpWidget() { qDebug() << "HelpWidget destroyed"; } ////////////////////////////////////////////////////////////////////////////// /////////////////////////////// PROTECTED //////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void HelpWidget::closeEvent(QCloseEvent *event) { this->hide(); event->ignore(); } void HelpWidget::hideEvent(QHideEvent *event) { QSettings settings; if (settings.isWritable()) { settings.setValue("HelpWidget/helpWindowSize", this->size()); } event->accept(); } dianara-v1.3.2/src/helpwidget.h000664 000764 000764 00000002743 12451104253 015775 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef HELPWIDGET_H #define HELPWIDGET_H #include #include #include #include #include #include #include #include #include class HelpWidget : public QWidget { Q_OBJECT public: explicit HelpWidget(QWidget *parent = 0); ~HelpWidget(); signals: public slots: protected: virtual void closeEvent(QCloseEvent *event); virtual void hideEvent(QHideEvent *event); private: QVBoxLayout *mainLayout; QTextBrowser *helpTextBrowser; QPushButton *closeButton; QAction *closeAction; }; #endif // HELPWIDGET_H dianara-v1.3.2/src/emailchanger.cpp000644 000764 000764 00000014341 12573333661 016622 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "emailchanger.h" EmailChanger::EmailChanger(QString explanation, PumpController *pumpController, QWidget *parent) : QWidget(parent) { this->setWindowTitle(tr("Change E-mail Address") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("view-pim-mail")); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::WindowModal); this->setMinimumSize(400, 300); this->pController = pumpController; this->infoLabel = new QLabel(explanation + ".", this); infoLabel->setWordWrap(true); this->mailLineEdit = new QLineEdit(this); connect(mailLineEdit, SIGNAL(textEdited(QString)), this, SLOT(validateFields())); this->mailRepeatLineEdit = new QLineEdit(this); connect(mailRepeatLineEdit, SIGNAL(textEdited(QString)), this, SLOT(validateFields())); this->passwordLineEdit = new QLineEdit(this); passwordLineEdit->setEchoMode(QLineEdit::Password); connect(passwordLineEdit, SIGNAL(textEdited(QString)), this, SLOT(validateFields())); this->errorsLabel = new QLabel(this); errorsLabel->setAlignment(Qt::AlignCenter); errorsLabel->setWordWrap(true); QFont errorsFont; errorsFont.setPointSize(errorsFont.pointSize() - 1); errorsFont.setItalic(true); errorsLabel->setFont(errorsFont); this->changeButton = new QPushButton(QIcon::fromTheme("document-save", QIcon(":/images/button-save.png")), tr("Change"), this); this->changeButton->setDisabled(true); // Initially disabled connect(changeButton, SIGNAL(clicked()), this, SLOT(changeEmail())); this->cancelButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("&Cancel"), this); connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelDialog())); // ESC to cancel, too cancelAction = new QAction(this); cancelAction->setShortcut(QKeySequence(Qt::Key_Escape)); connect(cancelAction, SIGNAL(triggered()), this, SLOT(cancelDialog())); this->addAction(cancelAction); // Layout this->middleLayout = new QFormLayout(); middleLayout->addRow(tr("E-mail Address:"), mailLineEdit); middleLayout->addRow(tr("Again:"), mailRepeatLineEdit); middleLayout->addRow(tr("Your Password:"), passwordLineEdit); this->bottomLayout = new QHBoxLayout(); bottomLayout->setAlignment(Qt::AlignRight); bottomLayout->addWidget(changeButton); bottomLayout->addWidget(cancelButton); this->mainLayout = new QVBoxLayout(); mainLayout->addWidget(infoLabel); mainLayout->addSpacing(8); mainLayout->addStretch(1); mainLayout->addLayout(middleLayout); mainLayout->addStretch(1); mainLayout->addWidget(errorsLabel, 2); mainLayout->addStretch(1); mainLayout->addSpacing(8); mainLayout->addLayout(bottomLayout); this->setLayout(mainLayout); qDebug() << "EmailChanger created"; } EmailChanger::~EmailChanger() { qDebug() << "EmailChanger destroyed"; } void EmailChanger::setCurrentEmail(QString email) { this->currentEmail = email; this->mailLineEdit->setText(this->currentEmail); } /****************************************************************************/ /******************************** SLOTS *************************************/ /****************************************************************************/ void EmailChanger::validateFields() { bool validFields = true; QString mail1 = this->mailLineEdit->text().trimmed(); QString mail2 = this->mailRepeatLineEdit->text().trimmed(); QString errorString = "
                "; // Both e-mails must be equal if (mail1 != mail2) { errorString.append("
              • " + tr("E-mail addresses don't match!") + "
              • "); validFields = false; } // Password can't be empty if (this->passwordLineEdit->text().isEmpty()) { errorString.append("
              • " + tr("Password is empty!") + "
              • "); validFields = false; } errorString.append("
              "); this->errorsLabel->setText(errorString); this->changeButton->setEnabled(validFields); } void EmailChanger::changeEmail() { this->currentEmail = this->mailLineEdit->text().trimmed(); this->pController->updateUserEmail(this->currentEmail, this->passwordLineEdit->text()); this->cancelDialog(); } void EmailChanger::cancelDialog() { this->mailRepeatLineEdit->clear(); this->passwordLineEdit->clear(); this->errorsLabel->clear(); this->changeButton->setDisabled(true); // Ensure good e-mail is set, in case dialog was cancelled with bad value this->mailLineEdit->setText(currentEmail); this->mailLineEdit->setFocus(); this->hide(); } /****************************************************************************/ /****************************** PROTECTED ***********************************/ /****************************************************************************/ void EmailChanger::closeEvent(QCloseEvent *event) { event->ignore(); this->cancelDialog(); } dianara-v1.3.2/src/groupsmanager.h000664 000764 000764 00000003364 12451104032 016506 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef GROUPSMANAGER_H #define GROUPSMANAGER_H #include #include #include #include #include #include #include #include "pumpcontroller.h" class GroupsManager : public QWidget { Q_OBJECT public: explicit GroupsManager(PumpController *pumpController, QWidget *parent = 0); ~GroupsManager(); signals: public slots: void createGroup(); void deleteGroup(); void joinGroup(); void leaveGroup(); private: QVBoxLayout *mainLayout; QLineEdit *newGroupNameLineEdit; QLineEdit *newGroupSummaryLineEdit; QLineEdit *newGroupDescLineEdit; QPushButton *createGroupButton; QLineEdit *joinLeaveGroupLineEdit; QPushButton *joinGroupButton; QPushButton *leaveGroupButton; QAction *closeAction; PumpController *pController; }; #endif // GROUPSMANAGER_H dianara-v1.3.2/src/groupsmanager.cpp000664 000764 000764 00000011703 12451104034 017037 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "groupsmanager.h" GroupsManager::GroupsManager(PumpController *pumpController, QWidget *parent) : QWidget(parent) { this->pController = pumpController; this->setWindowTitle("GROUPS MANAGER" " - Dianara"); this->setWindowIcon(QIcon::fromTheme("user-group-properties")); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::ApplicationModal); this->setMinimumSize(400, 400); this->newGroupNameLineEdit = new QLineEdit(); newGroupNameLineEdit->setPlaceholderText("Name for the new group"); this->newGroupSummaryLineEdit = new QLineEdit(); newGroupSummaryLineEdit->setPlaceholderText("Summary"); this->newGroupDescLineEdit = new QLineEdit(); newGroupDescLineEdit->setPlaceholderText("Longer description of the group"); this->createGroupButton = new QPushButton(QIcon::fromTheme("user-group-new"), "&CREATE GROUP"); connect(createGroupButton, SIGNAL(clicked()), this, SLOT(createGroup())); this->joinLeaveGroupLineEdit = new QLineEdit(); joinLeaveGroupLineEdit->setPlaceholderText("ID of the group you wish to join or leave"); this->joinGroupButton = new QPushButton(QIcon::fromTheme("list-add-user"), "&JOIN!"); connect(joinGroupButton, SIGNAL(clicked()), this, SLOT(joinGroup())); this->leaveGroupButton = new QPushButton(QIcon::fromTheme("list-remove-user"), "&LEAVE"); connect(leaveGroupButton, SIGNAL(clicked()), this, SLOT(leaveGroup())); closeAction = new QAction(this); closeAction->setShortcut(QKeySequence(Qt::Key_Escape)); connect(closeAction, SIGNAL(triggered()), this, SLOT(hide())); this->addAction(closeAction); // Layout this->mainLayout = new QVBoxLayout(); mainLayout->setAlignment(Qt::AlignTop); mainLayout->addWidget(newGroupNameLineEdit); mainLayout->addWidget(newGroupSummaryLineEdit); mainLayout->addWidget(newGroupDescLineEdit); mainLayout->addWidget(createGroupButton); mainLayout->addStretch(1); mainLayout->addWidget(joinLeaveGroupLineEdit); mainLayout->addWidget(joinGroupButton); mainLayout->addWidget(leaveGroupButton); this->setLayout(mainLayout); qDebug() << "GroupsManager created"; } GroupsManager::~GroupsManager() { qDebug() << "GroupsManager destroyed"; } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////// SLOTS ///////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void GroupsManager::createGroup() { if (newGroupNameLineEdit->text().trimmed().isEmpty()) { QMessageBox::warning(this, "ERROR", "Groups require a name."); return; } qDebug() << "Creating group..."; this->pController->createGroup(this->newGroupNameLineEdit->text().trimmed(), this->newGroupSummaryLineEdit->text().trimmed(), this->newGroupDescLineEdit->text().trimmed()); this->newGroupNameLineEdit->clear(); this->newGroupSummaryLineEdit->clear(); this->newGroupDescLineEdit->clear(); } void GroupsManager::deleteGroup() { } void GroupsManager::joinGroup() { if (joinLeaveGroupLineEdit->text().trimmed().isEmpty()) { QMessageBox::warning(this, "ERROR", "ID of group to join is empty."); return; } qDebug() << "Joining group..."; this->pController->joinGroup(this->joinLeaveGroupLineEdit->text().trimmed()); this->joinLeaveGroupLineEdit->clear(); } void GroupsManager::leaveGroup() { if (joinLeaveGroupLineEdit->text().trimmed().isEmpty()) { QMessageBox::warning(this, "ERROR", "ID of group to leave is empty."); return; } qDebug() << "Leaving group..."; this->pController->leaveGroup(this->joinLeaveGroupLineEdit->text().trimmed()); this->joinLeaveGroupLineEdit->clear(); } dianara-v1.3.2/src/globalobject.h000644 000764 000764 00000013406 12573333661 016300 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef GLOBALOBJECT_H #define GLOBALOBJECT_H #include #include #include #include #include #include #include class GlobalObject : public QObject { Q_OBJECT public: explicit GlobalObject(QObject *parent = 0); ~GlobalObject(); // General options void syncGeneralSettings(); // Font options void syncFontSettings(QString postTitleFont, QString postContentsFont, QString commentsFont, QString minorFeedFont); QString getPostTitleFont(); QString getPostContentsFont(); QString getCommentsFont(); QString getMinorFeedFont(); // Color options void syncColorSettings(QStringList newColorList); QStringList getColorsList(); QString getColor(int colorIndex); // Timeline options void syncTimelinesSettings(int pppMain, int pppOther, int minorFeedSnippets, int snippetsChars, bool showDeleted, bool hideDuplicates, bool jumpToNew); int getPostsPerPageMain(); int getPostsPerPageOther(); int getMinorFeedSnippetsType(); int getSnippetsCharLimit(); bool getShowDeleted(); bool getHideDuplicates(); bool getJumpToNewOnUpdate(); // Post options void syncPostSettings(int postAvatarSizeIndex, bool extendedShares, bool showExtraInfo, bool hlAuthorComments, bool hlOwnComments, bool postIgnoreSslInImages); int getPostAvatarSizeIndex(); QSize getPostAvatarSize(); bool getPostExtendedShares(); bool getPostShowExtraInfo(); bool getPostHLAuthorComments(); bool getPostHLOwnComments(); bool getPostIgnoreSslInImages(); // Composer options void syncComposerSettings(bool publicPosts, bool filenameAsTitle, bool showCharCounter); bool getPublicPostsByDefault(); bool getUseFilenameAsTitle(); bool getShowCharacterCounter(); // Privacy options void syncPrivacySettings(bool silentFollows, bool silentLists, bool silentLiking); bool getSilentFollows(); bool getSilentLists(); bool getSilentLikes(); // Notification options void syncNotificationSettings(); // Tray options void syncTrayOptions(bool hideInTrayStartup); bool getHideInTray(); /////////////////////////////////////////////////////////////////////////// void createMessageForContact(QString id, QString name, QString url); void browseUserMessages(QString userId, QString userName, QIcon userAvatar, QString userOutbox); void editPost(QString originalPostId, QString type, QString title, QString contents); QStandardItemModel *getNickCompletionModel(); void addToNickCompletionModel(QString id, QString name, QString url); void removeFromNickCompletionModel(QString id); void clearNickCompletionModel(); void sortNickCompletionModel(); void setStatusMessage(QString message); void logMessage(QString message, QString url=""); void storeTimelineHeight(int height); int getTimelineHeight(); signals: void messagingModeRequested(QString id, QString name, QString url); void userTimelineRequested(QString userId, QString userName, QIcon userAvatar, QString userOutbox); void postEditRequested(QString originalPostId, QString type, QString title, QString contents); void messageForStatusBar(QString message); void messageForLog(QString message, QString url); public slots: private: // General options // Font options QString postTitleFontInfo; QString postContentsFontInfo; QString commentsFontInfo; QString minorFeedFontInfo; // Color options QStringList colorsList; // Timeline options int postsPerPageMain; int postsPerPageOther; int minorFeedSnippetsType; int snippetsCharLimit; bool showDeletedPosts; bool hideDuplicatedPosts; bool jumpToNewOnUpdate; // Post options int postAvatarSizeIndex; QSize postAvatarSize; bool postExtendedShares; bool postShowExtraInfo; bool postHLAuthorComments; bool postHLOwnComments; bool postIgnoreSslInImages; // Composer options bool publicPostsByDefault; bool useFilenameAsTitle; bool showCharacterCounter; // Privacy options bool silentFollowing; bool silentListsHandling; bool silentLiking; // Notification options // Tray options bool hideInTray; ////////////////////////////////////////////////////////////////////////// // Other stuff QStandardItemModel *nickCompletionModel; int timelineHeight; }; #endif // GLOBALOBJECT_H dianara-v1.3.2/src/globalobject.cpp000644 000764 000764 00000034470 12612702037 016626 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "globalobject.h" GlobalObject::GlobalObject(QObject *parent) : QObject(parent) { QSettings settings; settings.beginGroup("Configuration"); /////////////////////////////////////////////////////////////////// GENERAL ///////////////////////////////////////////////////////////////////// FONTS QFont defaultTitleFont; // 1 point larger defaultTitleFont.setPointSize(defaultTitleFont.pointSize() + 1); defaultTitleFont.setBold(true); QFont defaultContentFont; // Just the default text size QFont defaultCommentsFont; // 1 point smaller defaultCommentsFont.setPointSize(defaultCommentsFont.pointSize() - 1); QFont defaultMinorFeedFont; // 2 points smaller defaultMinorFeedFont.setPointSize(defaultMinorFeedFont.pointSize() - 2); this->syncFontSettings(settings.value("font1", defaultTitleFont).toString(), settings.value("font2", defaultContentFont).toString(), settings.value("font3", defaultCommentsFont).toString(), settings.value("font4", defaultMinorFeedFont).toString()); //////////////////////////////////////////////////////////////////// COLORS this->colorsList.clear(); // Defaults: colorsList << settings.value("color1", "#DD2030").toString() // Red << settings.value("color2", "#60AADD").toString() // Blue << settings.value("color3", "#DDCC10").toString() // Yellow << settings.value("color4", "#10BB10").toString() // Green << settings.value("color5").toString() << settings.value("color6").toString(); // no need to call this->syncColorSettings() now... ///////////////////////////////////////////////////////////////// TIMELINES int pppMain = qBound(5, settings.value("postsPerPageMain", 20).toInt(), 50); int pppOther = qBound(1, settings.value("postsPerPageOther", 5).toInt(), 30); this->syncTimelinesSettings(pppMain, pppOther, settings.value("minorFeedSnippets", 0).toInt(), settings.value("snippetsCharLimit", 200).toInt(), settings.value("showDeletedPosts", false).toBool(), settings.value("hideDuplicatedPosts", false).toBool(), settings.value("jumpToNewOnUpdate", false).toBool()); ///////////////////////////////////////////////////////////////////// POSTS this->syncPostSettings(settings.value("postAvatarSizeIndex", 2).toInt(), // 3rd (64x64) by default settings.value("postExtendedShares", true).toBool(), settings.value("postShowExtraInfo", false).toBool(), settings.value("postHLAuthorComments", true).toBool(), settings.value("postHLOwnComments", true).toBool(), settings.value("postIgnoreSslInImages", false).toBool()); ////////////////////////////////////////////////////////////////// COMPOSER this->syncComposerSettings(settings.value("publicPosts", false).toBool(), settings.value("useFilenameAsTitle", false).toBool(), settings.value("showCharacterCounter", false).toBool()); /////////////////////////////////////////////////////////////////// PRIVACY this->syncPrivacySettings(settings.value("silentFollows", false).toBool(), settings.value("silentLists", true).toBool(), settings.value("silentLikes", false).toBool()); ///////////////////////////////////////////////////////////// NOTIFICATIONS this->syncNotificationSettings(); // Dummy, TODO ////////////////////////////////////////////////////////////////////// TRAY this->syncTrayOptions(settings.value("systrayHideInTray", false).toBool()); // TODO: load the other tray options settings.endGroup(); // Model for nick completion used by Composer this->nickCompletionModel = new QStandardItemModel(this); nickCompletionModel->setColumnCount(2); // Timeline height used to calculate maximum optimal height for post contents this->timelineHeight = 400; // Some initial value qDebug() << "GlobalObject created"; } GlobalObject::~GlobalObject() { qDebug() << "GlobalObject destroyed"; } // General options void GlobalObject::syncGeneralSettings() { // TODO } // Font options void GlobalObject::syncFontSettings(QString postTitleFont, QString postContentsFont, QString commentsFont, QString minorFeedFont) { this->postTitleFontInfo = postTitleFont; this->postContentsFontInfo = postContentsFont; this->commentsFontInfo = commentsFont; this->minorFeedFontInfo = minorFeedFont; qDebug() << "GlobalObject::syncFontSettings() - font info sync'd"; } QString GlobalObject::getPostTitleFont() { return this->postTitleFontInfo; } QString GlobalObject::getPostContentsFont() { return this->postContentsFontInfo; } QString GlobalObject::getCommentsFont() { return this->commentsFontInfo; } QString GlobalObject::getMinorFeedFont() { return this->minorFeedFontInfo; } // Color options void GlobalObject::syncColorSettings(QStringList newColorList) { this->colorsList = newColorList; qDebug() << "GlobalObject::syncColorSettings() - color list sync'd"; } QStringList GlobalObject::getColorsList() { return this->colorsList; } QString GlobalObject::getColor(int colorIndex) { QString color; if (colorIndex >= 0 && colorIndex < this->colorsList.length()) { color = this->colorsList.at(colorIndex); if (!QColor::isValidColor(color)) // If invalid, clear it { color.clear(); } } return color; } // Timeline options void GlobalObject::syncTimelinesSettings(int pppMain, int pppOther, int minorFeedSnippets, int snippetsChars, bool showDeleted, bool hideDuplicates, bool jumpToNew) { this->postsPerPageMain = pppMain; this->postsPerPageOther = pppOther; this->minorFeedSnippetsType = minorFeedSnippets; this->snippetsCharLimit = snippetsChars; this->showDeletedPosts = showDeleted; this->hideDuplicatedPosts = hideDuplicates; this->jumpToNewOnUpdate = jumpToNew; // TODO: more... } int GlobalObject::getPostsPerPageMain() { return this->postsPerPageMain; } int GlobalObject::getPostsPerPageOther() { return this->postsPerPageOther; } int GlobalObject::getMinorFeedSnippetsType() { return this->minorFeedSnippetsType; } int GlobalObject::getSnippetsCharLimit() { return this->snippetsCharLimit; } bool GlobalObject::getShowDeleted() { return this->showDeletedPosts; } bool GlobalObject::getHideDuplicates() { return this->hideDuplicatedPosts; } bool GlobalObject::getJumpToNewOnUpdate() { return this->jumpToNewOnUpdate; } // Post options void GlobalObject::syncPostSettings(int postAvatarSizeIndex, bool extendedShares, bool showExtraInfo, bool hlAuthorComments, bool hlOwnComments, bool ignoreSslInImages) { this->postAvatarSizeIndex = postAvatarSizeIndex; int pixelSize; switch (postAvatarSizeIndex) { case 0: pixelSize = 32; break; // case 1 = default case 2: pixelSize = 64; break; case 3: pixelSize = 96; break; case 4: pixelSize = 128; break; case 5: pixelSize = 256; break; default: // index = 1 or invalid option pixelSize = 48; } this->postAvatarSize = QSize(pixelSize, pixelSize); this->postExtendedShares = extendedShares; this->postShowExtraInfo = showExtraInfo; this->postHLAuthorComments = hlAuthorComments; this->postHLOwnComments = hlOwnComments; this->postIgnoreSslInImages = ignoreSslInImages; } int GlobalObject::getPostAvatarSizeIndex() { return this->postAvatarSizeIndex; } QSize GlobalObject::getPostAvatarSize() { return this->postAvatarSize; } bool GlobalObject::getPostExtendedShares() { return this->postExtendedShares; } bool GlobalObject::getPostShowExtraInfo() { return this->postShowExtraInfo; } bool GlobalObject::getPostHLAuthorComments() { return this->postHLAuthorComments; } bool GlobalObject::getPostHLOwnComments() { return this->postHLOwnComments; } bool GlobalObject::getPostIgnoreSslInImages() { return this->postIgnoreSslInImages; } // Composer options void GlobalObject::syncComposerSettings(bool publicPosts, bool filenameAsTitle, bool showCharCounter) { this->publicPostsByDefault = publicPosts; this->useFilenameAsTitle = filenameAsTitle; this->showCharacterCounter = showCharCounter; } bool GlobalObject::getPublicPostsByDefault() { return this->publicPostsByDefault; } bool GlobalObject::getUseFilenameAsTitle() { return this->useFilenameAsTitle; } bool GlobalObject::getShowCharacterCounter() { return this->showCharacterCounter; } // Privacy options void GlobalObject::syncPrivacySettings(bool silentFollows, bool silentLists, bool silentLikes) { this->silentFollowing = silentFollows; this->silentListsHandling = silentLists; this->silentLiking = silentLikes; } bool GlobalObject::getSilentFollows() { return this->silentFollowing; } bool GlobalObject::getSilentLists() { return this->silentListsHandling; } bool GlobalObject::getSilentLikes() { return this->silentLiking; } // Notification options void GlobalObject::syncNotificationSettings() { // TODO, still handled elsewhere } // Tray options void GlobalObject::syncTrayOptions(bool hideInTrayStartup) { this->hideInTray = hideInTrayStartup; // TODO, some still handled elsewhere } bool GlobalObject::getHideInTray() { return this->hideInTray; } /////////////////////////////////////////////////////////////////////////////// void GlobalObject::createMessageForContact(QString id, QString name, QString url) { // Send signal to be caught by Publisher() emit messagingModeRequested(id, name, url); qDebug() << "GlobalObject; asking for Messaging mode for " << name << id << url; } void GlobalObject::browseUserMessages(QString userId, QString userName, QIcon userAvatar, QString userOutbox) { // Signal to be caught by MainWindow emit userTimelineRequested(userId, userName, userAvatar, userOutbox); } void GlobalObject::editPost(QString originalPostId, QString type, QString title, QString contents) { // Signal to be caught by Publisher emit postEditRequested(originalPostId, type, title, contents); qDebug() << "GlobalObject; asking to edit post: " << originalPostId << title << type; } QStandardItemModel *GlobalObject::getNickCompletionModel() { return this->nickCompletionModel; } void GlobalObject::addToNickCompletionModel(QString id, QString name, QString url) { QStandardItem *itemName = new QStandardItem(name); itemName->setData(id, Qt::UserRole + 1); itemName->setData(url, Qt::UserRole + 2); QStandardItem *itemId = new QStandardItem(id); QList itemLine; itemLine.append(itemName); itemLine.append(itemId); this->nickCompletionModel->appendRow(itemLine); } void GlobalObject::removeFromNickCompletionModel(QString id) { QList allNicks; allNicks = nickCompletionModel->findItems("", Qt::MatchContains); foreach (QStandardItem *item, allNicks) { //qDebug() << item->data(Qt::UserRole + 1).toString(); if (item->data(Qt::UserRole + 1).toString() == id) { this->nickCompletionModel->removeRow(item->row()); } } } void GlobalObject::clearNickCompletionModel() { // clear() seems to delete the items properly this->nickCompletionModel->clear(); } void GlobalObject::sortNickCompletionModel() { this->nickCompletionModel->sort(0); } // Change status bar message in main window void GlobalObject::setStatusMessage(QString message) { emit messageForStatusBar(message); } // Add a log message to the log viewer void GlobalObject::logMessage(QString message, QString url) { emit messageForLog(message, url); } void GlobalObject::storeTimelineHeight(int height) { // Substract some pixels to account for the row of buttons, etc. this->timelineHeight = qMax(height - 80, 50); // Never less than 50, but window should never be that small } int GlobalObject::getTimelineHeight() { return this->timelineHeight; } dianara-v1.3.2/src/pageselector.h000664 000764 000764 00000003601 12537622162 016321 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef PAGESELECTOR_H #define PAGESELECTOR_H #include #include #include #include #include #include #include #include #include class PageSelector : public QWidget { Q_OBJECT public: explicit PageSelector(QWidget *parent = 0); ~PageSelector(); void showForPage(int currentPage, int totalPageCount); signals: void pageJumpRequested(int pageNumber); public slots: void setToFirstPage(); void setToLastPage(); void goToPage(); void onPageNumberEntered(); private: QVBoxLayout *mainLayout; QHBoxLayout *topLayout; QHBoxLayout *middleLayout; QHBoxLayout *bottomLayout; QLabel *messageLabel; QSpinBox *pageNumberSpinbox; QLabel *rangeLabel; QPushButton *firstButton; QPushButton *lastButton; QLabel *newerLabel; QSlider *pageNumberSlider; QLabel *olderLabel; QPushButton *goButton; QPushButton *closeButton; QAction *closeAction; }; #endif // PAGESELECTOR_H dianara-v1.3.2/src/pageselector.cpp000644 000764 000764 00000015435 12613231576 016662 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "pageselector.h" PageSelector::PageSelector(QWidget *parent) : QWidget(parent) { this->setWindowTitle(tr("Jump to page") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("go-next-view-page", QIcon(":/images/button-next.png"))); this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::ApplicationModal); // Top this->messageLabel = new QLabel(tr("Page number:"), this); this->pageNumberSpinbox = new QSpinBox(this); pageNumberSpinbox->setRange(1, 1); // 1-totalPages, but initially 1-1 pageNumberSpinbox->setValue(1); connect(pageNumberSpinbox, SIGNAL(editingFinished()), this, SLOT(onPageNumberEntered())); this->rangeLabel = new QLabel(this); this->firstButton = new QPushButton(QIcon::fromTheme("go-first-view-page", QIcon(":/images/button-previous.png")), tr("&First", "As in: first page"), this); connect(firstButton, SIGNAL(clicked()), this, SLOT(setToFirstPage())); this->lastButton = new QPushButton(QIcon::fromTheme("go-last-view-page", QIcon(":/images/button-next.png")), tr("&Last", "As in: last page"), this); connect(lastButton, SIGNAL(clicked()), this, SLOT(setToLastPage())); // Middle this->newerLabel = new QLabel("< " + tr("Newer", "As in: newer pages"), this); this->pageNumberSlider = new QSlider(Qt::Horizontal, this); pageNumberSlider->setRange(1, 1); // Initially pageNumberSlider->setValue(1); pageNumberSlider->setTickPosition(QSlider::TicksBelow); // Make spinbox and slider keep in sync connect(pageNumberSpinbox, SIGNAL(valueChanged(int)), pageNumberSlider, SLOT(setValue(int))); connect(pageNumberSlider, SIGNAL(valueChanged(int)), pageNumberSpinbox, SLOT(setValue(int))); this->olderLabel = new QLabel(tr("Older", "As in: older pages") + " >", this); // Bottom this->goButton = new QPushButton(QIcon::fromTheme("go-next-view-page", QIcon(":/images/button-next.png")), " " + tr("&Go") + " >> ", this); connect(goButton, SIGNAL(clicked()), this, SLOT(goToPage())); this->closeButton = new QPushButton(QIcon::fromTheme("dialog-cancel", QIcon(":/images/button-cancel.png")), tr("&Cancel"), this); connect(closeButton, SIGNAL(clicked()), this, SLOT(hide())); closeAction = new QAction(this); closeAction->setShortcut(QKeySequence(Qt::Key_Escape)); connect(closeAction, SIGNAL(triggered()), this, SLOT(hide())); this->addAction(closeAction); // Layout this->topLayout = new QHBoxLayout(); topLayout->addWidget(messageLabel); topLayout->addSpacing(4); topLayout->addWidget(pageNumberSpinbox); topLayout->addSpacing(4); topLayout->addWidget(rangeLabel); topLayout->addSpacing(8); topLayout->addStretch(1); topLayout->addWidget(firstButton); topLayout->addWidget(lastButton); this->middleLayout = new QHBoxLayout(); middleLayout->addWidget(newerLabel); middleLayout->addSpacing(4); middleLayout->addWidget(pageNumberSlider); middleLayout->addSpacing(4); middleLayout->addWidget(olderLabel); this->bottomLayout = new QHBoxLayout(); bottomLayout->setAlignment(Qt::AlignRight); bottomLayout->addWidget(goButton); bottomLayout->addWidget(closeButton); this->mainLayout = new QVBoxLayout(); mainLayout->addLayout(topLayout); mainLayout->addSpacing(24); mainLayout->addStretch(1); mainLayout->addLayout(middleLayout); mainLayout->addStretch(2); mainLayout->addSpacing(32); mainLayout->addLayout(bottomLayout); this->setLayout(mainLayout); qDebug() << "PageSelector created"; } PageSelector::~PageSelector() { qDebug() << "PageSelector destroyed"; } /* * To be called from TimeLine() * * Set current page based on current Timeline page, and total page count * */ void PageSelector::showForPage(int currentPage, int totalPageCount) { this->pageNumberSpinbox->setRange(1, totalPageCount); this->pageNumberSpinbox->setValue(currentPage); this->pageNumberSlider->setRange(1, totalPageCount); // Value will get sync'ed from pageNumberSpinbox this->pageNumberSlider->setTickInterval(totalPageCount / 4); // One tick every 1/4th this->rangeLabel->setText(QString("[ 1 - %1 ]") .arg(QLocale::system().toString(totalPageCount))); this->show(); this->pageNumberSpinbox->setFocus(); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////// SLOTS ///////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void PageSelector::setToFirstPage() { this->pageNumberSpinbox->setValue(this->pageNumberSpinbox->minimum()); } void PageSelector::setToLastPage() { this->pageNumberSpinbox->setValue(this->pageNumberSpinbox->maximum()); } void PageSelector::goToPage() { emit pageJumpRequested(this->pageNumberSpinbox->value()); this->hide(); } void PageSelector::onPageNumberEntered() { // If spinbox still has focus, it means ENTER was pressed if (this->pageNumberSpinbox->hasFocus()) { this->goToPage(); } // If not, it means focus when somewhere else, // which also causes editingFinished() to be emitted } dianara-v1.3.2/src/hclabel.h000664 000764 000764 00000003040 12451104034 015217 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef HCLABEL_H #define HCLABEL_H #include #include #include #include class HClabel : public QLabel { Q_OBJECT public: explicit HClabel(QString initialText = QString(), QWidget *parent = 0); ~HClabel(); void setHighlighted(bool highlighted); void setBaseText(QString text); void setExtendedText(QString text); void setExpanded(bool isExpanded); signals: void clicked(); public slots: void toggleContent(); protected: virtual void mousePressEvent(QMouseEvent *event); private: QString baseText; QString extendedText; bool expanded; QTimer *toggleTimer; }; #endif // HCLABEL_H dianara-v1.3.2/src/hclabel.cpp000664 000764 000764 00000011061 12522526524 015567 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "hclabel.h" HClabel::HClabel(QString initialText, QWidget *parent) : QLabel(parent) { this->setWordWrap(true); this->setAutoFillBackground(true); this->setTextFormat(Qt::RichText); this->setBaseText(initialText); this->setHighlighted(false); // Default this->setExpanded(false); this->toggleTimer = new QTimer(this); toggleTimer->setSingleShot(true); connect(toggleTimer, SIGNAL(timeout()), this, SLOT(toggleContent())); qDebug() << "HClabel created"; } HClabel::~HClabel() { qDebug() << "HClabel destroyed"; } void HClabel::setHighlighted(bool highlighted) { if (highlighted) { // Constant highlighting this->setStyleSheet("QLabel " "{ color: palette(highlighted-text); " " background-color: qlineargradient(spread:pad, " " x1:0, y1:0, x2:0, y2:1, " " stop:0 rgba(0, 0, 0, 0), " " stop:0.3 palette(highlight), " " stop:0.7 palette(highlight), " " stop:1 rgba(0, 0, 0, 0)); " " border-radius: 4px " "}" "QLabel:hover " "{ color: palette(highlighted-text); " " background-color: palette(highlight);" " border-radius: 4px " "}"); } else { // Highlight only on mouse hover this->setStyleSheet("QLabel " "{ background-color: transparent }" "QLabel:hover " "{ color: palette(highlighted-text); " " background-color: palette(highlight);" " border-radius: 4px " "}"); } } void HClabel::setBaseText(QString text) { this->baseText = text; if (extendedText.isEmpty() || !expanded) { this->setText(this->baseText); } else { this->setText(baseText + "
              " + extendedText + "
              "); } } void HClabel::setExtendedText(QString text) { this->baseText = this->text(); // Save it this->extendedText = text.trimmed(); } void HClabel::setExpanded(bool isExpanded) { this->expanded = isExpanded; } ////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// SLOTS ////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void HClabel::toggleContent() { if (expanded) { this->setText(baseText); } else { if (!this->extendedText.isEmpty()) { this->setText(baseText + "
              " + extendedText + "
              "); } } this->setExpanded(!expanded); } ////////////////////////////////////////////////////////////////////////////// /////////////////////////////// PROTECTED //////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void HClabel::mousePressEvent(QMouseEvent *event) { toggleTimer->start(200); // Don't emit the signal for now... we'll see if we need it //emit clicked(); event->ignore(); // Let the click pass through to the parent } dianara-v1.3.2/src/userposts.h000644 000764 000764 00000004414 12573333661 015717 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef USERPOSTS_H #define USERPOSTS_H #include #include #include #include #include #include #include #include #include "timeline.h" #include "filterchecker.h" #include "pumpcontroller.h" #include "globalobject.h" class UserPosts : public QWidget { Q_OBJECT public: UserPosts(QString userId, QString userName, QIcon userAvatar, QString userOutbox, PumpController *pumpController, GlobalObject *globalObject, FilterChecker *filterChecker, QWidget *parent); ~UserPosts(); signals: public slots: void fillTimeLine(QVariantList postList, QString previousLink, QString nextLink, int totalItems, QString url); void notifyTimelineUpdate(); void onTimelineFailed(); void scrollTimelineTo(QAbstractSlider::SliderAction sliderAction); protected: virtual void resizeEvent(QResizeEvent *event); virtual void closeEvent(QCloseEvent *event); virtual void keyPressEvent(QKeyEvent *event); private: QVBoxLayout *mainLayout; QHBoxLayout *bottomLayout; QScrollArea *scrollArea; TimeLine *timeline; QLabel *infoLabel; QPushButton *closeButton; QString timelineTitle; QString timelineUrl; QString userInfoString; PumpController *pController; GlobalObject *globalObj; }; #endif // USERPOSTS_H dianara-v1.3.2/src/userposts.cpp000644 000764 000764 00000016400 12600522460 016234 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "userposts.h" UserPosts::UserPosts(QString userId, QString userName, QIcon userAvatar, QString userOutbox, PumpController *pumpController, GlobalObject *globalObject, FilterChecker *filterChecker, QWidget *parent) : QWidget(parent) { this->pController = pumpController; this->globalObj = globalObject; this->setWindowFlags(Qt::Dialog); this->setWindowModality(Qt::WindowModal); this->timelineTitle = tr("Posts by %1").arg(userName); this->setWindowTitle(timelineTitle + " - Dianara"); this->setWindowIcon(userAvatar); this->setMinimumSize(200, 300); QSettings settings; this->resize(settings.value("UserTimeline/userTimelineSize", QSize(560, 720)).toSize()); timeline = new TimeLine(PumpController::UserTimelineRequest, this->pController, this->globalObj, filterChecker, this); this->scrollArea = new QScrollArea(this); this->scrollArea->setContentsMargins(1, 1, 1, 1); this->scrollArea->setWidget(timeline); this->scrollArea->setWidgetResizable(true); this->scrollArea->setFrameStyle(QFrame::Box | QFrame::Raised); this->scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(timeline, SIGNAL(scrollTo(QAbstractSlider::SliderAction)), this, SLOT(scrollTimelineTo(QAbstractSlider::SliderAction))); this->infoLabel = new QLabel(tr("Loading..."), this); this->closeButton = new QPushButton(QIcon::fromTheme("window-close", QIcon(":/images/button-close.png")), tr("&Close"), this); connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); // PumpController's connections connect(pController, SIGNAL(userTimelineReceived(QVariantList,QString,QString,int,QString)), this, SLOT(fillTimeLine(QVariantList,QString,QString,int,QString))); connect(pController, SIGNAL(userTimelineFailed()), this, SLOT(onTimelineFailed())); connect(pController, SIGNAL(commentsReceived(QVariantList,QString)), timeline, SLOT(setCommentsInPost(QVariantList,QString))); connect(timeline, SIGNAL(timelineRendered(PumpController::requestTypes,int,int,int,int,int)), this, SLOT(notifyTimelineUpdate())); // Initial load this->timeline->setCustomUrl(userOutbox); this->timeline->setDisabled(true); this->timeline->clearTimeLineContents(); // Hack to get comments properly resized // FIXME: On first load, should set showMessage=false so initial "Requesting..." is not erased // Not doing that for 1.3.1 due to problems pController->getFeed(PumpController::UserTimelineRequest, this->globalObj->getPostsPerPageMain(), userOutbox); // String shown at the bottom userInfoString = userName + " - " + userId; // kinda TMP -- FIXME // Layout this->bottomLayout = new QHBoxLayout(); bottomLayout->addWidget(infoLabel); bottomLayout->addStretch(1); bottomLayout->addWidget(closeButton); this->mainLayout = new QVBoxLayout(); mainLayout->setContentsMargins(2, 2, 2, 2); mainLayout->addWidget(scrollArea); mainLayout->addSpacing(6); mainLayout->addLayout(bottomLayout); this->setLayout(mainLayout); qDebug() << "UserPosts timeline container created for" << userName; } UserPosts::~UserPosts() { qDebug() << "UserPosts timeline container destroyed"; } ////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////// SLOTS ////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /* * Set contents of timeline only if the user timeline received matches this one. * Necessary when opening a user's timeline from another user's timeline * */ void UserPosts::fillTimeLine(QVariantList postList, QString previousLink, QString nextLink, int totalItems, QString url) { if (this->timelineUrl.isEmpty()) // Set it the first time { this->timelineUrl = url; } if (url == this->timelineUrl) { this->timeline->setTimeLineContents(postList, previousLink, nextLink, totalItems); } } void UserPosts::notifyTimelineUpdate() { QString message = tr("Received '%1'.").arg(this->timelineTitle); this->globalObj->setStatusMessage(message); this->globalObj->logMessage(message); QString postCount = QLocale::system().toString(timeline->getTotalPosts()); this->infoLabel->setText(this->userInfoString + " - " + tr("%1 posts").arg(postCount)); this->timeline->resizePosts(QList(), true); // resize all posts } // Set error messages if timeline fails to load void UserPosts::onTimelineFailed() { QString message = tr("Error loading the timeline"); this->infoLabel->setText(message + "."); this->timeline->showMessage(message); } // React to Control+PgUp/PgDn, etc. sent from TimeLine() void UserPosts::scrollTimelineTo(QAbstractSlider::SliderAction sliderAction) { this->scrollArea->verticalScrollBar()->triggerAction(sliderAction); } ////////////////////////////////////////////////////////////////////////////// /////////////////////////////////// PROTECTED //////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void UserPosts::resizeEvent(QResizeEvent *event) { this->timeline->resizePosts(QList(), true); // resizeAll=true event->accept(); } void UserPosts::closeEvent(QCloseEvent *event) { QSettings settings; if (settings.isWritable()) { settings.setValue("UserTimeline/userTimelineSize", this->size()); } delete this->timeline; this->deleteLater(); this->hide(); event->ignore(); } void UserPosts::keyPressEvent(QKeyEvent *event) { // ESC to close, if there are no comments in progress if (event->key() == Qt::Key_Escape && !this->timeline->commentingOnAnyPost()) { event->accept(); this->close(); } else { event->ignore(); } } dianara-v1.3.2/src/emailchanger.h000664 000764 000764 00000003710 12537323716 016267 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef EMAILCHANGER_H #define EMAILCHANGER_H #include #include #include #include #include #include #include #include #include #include "pumpcontroller.h" class EmailChanger : public QWidget { Q_OBJECT public: explicit EmailChanger(QString explanation, PumpController *pumpController, QWidget *parent = 0); ~EmailChanger(); void setCurrentEmail(QString email); signals: public slots: void validateFields(); void changeEmail(); void cancelDialog(); protected: virtual void closeEvent(QCloseEvent *event); private: QVBoxLayout *mainLayout; QFormLayout *middleLayout; QHBoxLayout *bottomLayout; QLabel *infoLabel; QLineEdit *mailLineEdit; QLineEdit *mailRepeatLineEdit; QLineEdit *passwordLineEdit; QLabel *errorsLabel; QPushButton *changeButton; QPushButton *cancelButton; QAction *cancelAction; QString currentEmail; PumpController *pController; }; #endif // EMAILCHANGER_H dianara-v1.3.2/src/siteuserslist.h000644 000764 000764 00000003362 12573333661 016573 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef SITEUSERSLIST_H #define SITEUSERSLIST_H #include #include #include #include #include #include #include "pumpcontroller.h" #include "globalobject.h" #include "contactlist.h" class SiteUsersList : public QWidget { Q_OBJECT public: explicit SiteUsersList(PumpController *pumpController, GlobalObject *globalObject, QWidget *parent = 0); ~SiteUsersList(); signals: public slots: void getList(); void setListContents(QVariantList userList, int totalUsers); void closeList(); private: QVBoxLayout *mainLayout; QHBoxLayout *middleLayout; QLabel *explanationLabel; QPushButton *getListButton; QLabel *serverInfoLabel; QPushButton *closeListButton; ContactList *userList; PumpController *pController; }; #endif // SITEUSERSLIST_H dianara-v1.3.2/src/dbusinterface.h000664 000764 000764 00000002510 12563404006 016452 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef DBUSINTERFACE_H #define DBUSINTERFACE_H #include #include #include class DBusInterface : public QObject { Q_OBJECT // Optional, but makes the D-Bus exported methods look nicer Q_CLASSINFO("D-Bus Interface", "org.nongnu.dianara") public: explicit DBusInterface(QObject *parent = 0); ~DBusInterface(); signals: public slots: void toggle(); void post(QString title, QString content); }; #endif // DBUSINTERFACE_H dianara-v1.3.2/src/dbusinterface.cpp000664 000764 000764 00000003346 12563406105 017017 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "dbusinterface.h" DBusInterface::DBusInterface(QObject *parent) : QObject(parent) { qDebug() << "DBusInterface created"; } DBusInterface::~DBusInterface() { qDebug() << "DBusInterface destroyed"; } /****************************************************************************/ /******************************** SLOTS *************************************/ /****************************************************************************/ void DBusInterface::toggle() { qDebug() << "DBusInterface::toggle()"; QMetaObject::invokeMethod(parent(), "toggleMainWindow"); } void DBusInterface::post(QString title, QString content) { qDebug() << "DBusInterface::post(); " << "Title:" << title << "; " << "Text:" << content; QMetaObject::invokeMethod(parent(), "startPost", Q_ARG(QString, title), Q_ARG(QString, content)); } dianara-v1.3.2/src/firstrunwizard.h000644 000764 000764 00000004377 12573333661 016755 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #ifndef FIRSTRUNWIZARD_H #define FIRSTRUNWIZARD_H #include #include #include #include #include #include #include #include #include #include "accountdialog.h" #include "profileeditor.h" #include "configdialog.h" #include "helpwidget.h" #include "globalobject.h" class FirstRunWizard : public QWidget { Q_OBJECT public: explicit FirstRunWizard(AccountDialog *accountDlg, ProfileEditor *profileEd, ConfigDialog *configDlg, HelpWidget *helpWdg, GlobalObject *globalObj, QWidget *parent = 0); ~FirstRunWizard(); signals: public slots: protected: virtual void closeEvent(QCloseEvent *event); private: QVBoxLayout *mainLayout; QHBoxLayout *bottomLayout; QLabel *explanationLabel; QPushButton *configureAccountButton; QLabel *editProfileLabel; QPushButton *editProfileButton; QLabel *publicPostsLabel; QCheckBox *publicPostsCheckbox; QPushButton *helpButton; QCheckBox *showAgainCheckbox; QPushButton *closeButton; // Widgets from MainWindow AccountDialog *accountDialog; ProfileEditor *profileEditor; ConfigDialog *configDialog; HelpWidget *helpWidget; GlobalObject *globalObject; }; #endif // FIRSTRUNWIZARD_H dianara-v1.3.2/src/siteuserslist.cpp000644 000764 000764 00000012724 12600560607 017121 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "siteuserslist.h" SiteUsersList::SiteUsersList(PumpController *pumpController, GlobalObject *globalObject, QWidget *parent) : QWidget(parent) { this->pController = pumpController; this->explanationLabel = new QLabel("

              " + tr("You can get a list of the newest users registered " "on your server by clicking the button below.") + "




              " + tr("More resources to find users:") + "" "
              ", this); explanationLabel->setWordWrap(true); explanationLabel->setAlignment(Qt::AlignTop); explanationLabel->setMargin(8); explanationLabel->setOpenExternalLinks(true); this->getListButton = new QPushButton(QIcon::fromTheme("system-users", QIcon(":/images/no-avatar.png")), tr("Get list of users from your server"), this); connect(getListButton, SIGNAL(clicked()), this, SLOT(getList())); this->serverInfoLabel = new QLabel(this); serverInfoLabel->setWordWrap(true); serverInfoLabel->setAlignment(Qt::AlignCenter); serverInfoLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); serverInfoLabel->setMargin(4); serverInfoLabel->hide(); this->closeListButton = new QPushButton(QIcon::fromTheme("window-close", QIcon(":/images/button-close.png")), "", this); closeListButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); closeListButton->setToolTip(tr("Close list")); connect(closeListButton, SIGNAL(clicked()), this, SLOT(closeList())); closeListButton->hide(); this->userList = new ContactList(this->pController, globalObject, "site-users", this); this->userList->hide(); connect(pController, SIGNAL(siteUserListReceived(QVariantList,int)), this, SLOT(setListContents(QVariantList,int))); this->middleLayout = new QHBoxLayout(); middleLayout->addWidget(serverInfoLabel); middleLayout->addWidget(closeListButton); this->mainLayout = new QVBoxLayout(); this->mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addWidget(explanationLabel); mainLayout->addWidget(getListButton, 1, Qt::AlignCenter); mainLayout->addLayout(middleLayout); mainLayout->addWidget(userList); this->setLayout(mainLayout); qDebug() << "SiteUsersList created"; } SiteUsersList::~SiteUsersList() { qDebug() << "SiteUsersList destroyed"; } /*****************************************************************************/ /*********************************** SLOTS ***********************************/ /*****************************************************************************/ void SiteUsersList::getList() { this->pController->getSiteUserList(); this->explanationLabel->hide(); this->getListButton->hide(); this->serverInfoLabel->setText(tr("Loading...")); this->serverInfoLabel->show(); this->closeListButton->show(); this->userList->clearListContents(); this->userList->show(); } void SiteUsersList::setListContents(QVariantList userList, int totalUsers) { this->serverInfoLabel->setText(tr("%1 users in %2", "%1 = user count, %2 = server name") .arg(QLocale::system().toString(totalUsers)) .arg(this->pController->currentServerUrl())); this->userList->setListContents(userList); } void SiteUsersList::closeList() { this->explanationLabel->show(); this->getListButton->show(); this->serverInfoLabel->hide(); this->closeListButton->hide(); this->userList->clearListContents(); this->userList->hide(); } dianara-v1.3.2/src/firstrunwizard.cpp000644 000764 000764 00000017445 12613336622 017303 0ustar00janjan000000 000000 /* * This file is part of Dianara * Copyright 2012-2015 JanKusanagi JRR * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . */ #include "firstrunwizard.h" FirstRunWizard::FirstRunWizard(AccountDialog *accountDlg, ProfileEditor *profileEd, ConfigDialog *configDlg, HelpWidget *helpWdg, GlobalObject *globalObj, QWidget *parent) : QWidget(parent) { this->setWindowTitle(tr("Welcome Wizard") + " - Dianara"); this->setWindowIcon(QIcon::fromTheme("tools-wizard")); this->setWindowFlags(Qt::Window); this->setMinimumSize(420, 460); this->resize(520, 620); this->accountDialog = accountDlg; this->profileEditor = profileEd; this->configDialog = configDlg; this->helpWidget = helpWdg; this->globalObject = globalObj; this->explanationLabel = new QLabel("" + tr("Welcome to Dianara!") + "" + "

              " + tr("This wizard will help you " "get started.") + " " + tr("You can access this window again " "at any time from the Help menu.") + "

              " + tr("The first step is setting up " "your account, by using the " "following button:"), this); explanationLabel->setWordWrap(true); explanationLabel->setAlignment(Qt::AlignTop); explanationLabel->setOpenExternalLinks(true); // Config account this->configureAccountButton = new QPushButton(QIcon::fromTheme("dialog-password", QIcon(":/images/button-password.png")), tr("Configure your &account"), this); connect(configureAccountButton, SIGNAL(clicked()), accountDialog, SLOT(show())); // Edit profile this->editProfileLabel = new QLabel(tr("Once you have configured your " "account, it's recommended that you " "edit your profile and add an avatar " "and some other information, if you " "haven't done so already."), this); editProfileLabel->setWordWrap(true); editProfileLabel->setAlignment(Qt::AlignTop); this->editProfileButton = new QPushButton(QIcon::fromTheme("user-properties", QIcon(":/images/no-avatar.png")), tr("&Edit your profile"), this); //this->editProfileButton->setDisabled(true); // TMP FIXME connect(editProfileButton, SIGNAL(clicked()), profileEditor, SLOT(show())); // Public posting this->publicPostsLabel = new QLabel(tr("By default, Dianara will post only " "to your followers, but it's " "recommended that you post to " "Public, at least sometimes."), this); publicPostsLabel->setWordWrap(true); publicPostsLabel->setAlignment(Qt::AlignTop); this->publicPostsCheckbox = new QCheckBox(tr("Post to &Public by default"), this); publicPostsCheckbox->setChecked(globalObject->getPublicPostsByDefault()); // Access to program help this->helpButton = new QPushButton(QIcon::fromTheme("system-help", QIcon(":/images/menu-find.png")), tr("Open general program &help window"), this); connect(helpButton, SIGNAL(clicked()), helpWidget, SLOT(show())); // Bottom this->showAgainCheckbox = new QCheckBox(tr("&Show this again next time " "Dianara starts"), this); this->closeButton = new QPushButton(QIcon::fromTheme("window-close", QIcon(":/images/button-close.png")), tr("&Close"), this); connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); // Layout this->bottomLayout = new QHBoxLayout(); this->bottomLayout->addWidget(showAgainCheckbox); this->bottomLayout->addWidget(closeButton, 0, Qt::AlignRight); this->mainLayout = new QVBoxLayout(); mainLayout->addWidget(explanationLabel); mainLayout->addSpacing(12); mainLayout->addWidget(configureAccountButton, 0, Qt::AlignCenter); mainLayout->addStretch(1); mainLayout->addWidget(editProfileLabel); mainLayout->addSpacing(12); mainLayout->addWidget(editProfileButton, 0, Qt::AlignCenter); mainLayout->addStretch(1); mainLayout->addWidget(publicPostsLabel); mainLayout->addSpacing(12); mainLayout->addWidget(publicPostsCheckbox, 0, Qt::AlignCenter); mainLayout->addStretch(1); mainLayout->addSpacing(16); mainLayout->addWidget(helpButton, 0, Qt::AlignCenter); mainLayout->addStretch(1); mainLayout->addSpacing(16); mainLayout->addLayout(bottomLayout); this->setLayout(mainLayout); QSettings settings; showAgainCheckbox->setChecked(settings.value("FirstRunWizard/showWizard", true).toBool()); qDebug() << "FirstRunWizard created"; } FirstRunWizard::~FirstRunWizard() { qDebug() << "FirstRunWizard destroyed"; } /*****************************************************************************/ /*********************************** SLOTS ***********************************/ /*****************************************************************************/ /*****************************************************************************/ /********************************* PROTECTED *********************************/ /*****************************************************************************/ void FirstRunWizard::closeEvent(QCloseEvent *event) { QSettings settings; settings.setValue("FirstRunWizard/showWizard", this->showAgainCheckbox->isChecked()); settings.sync(); // Sync public posting option this->configDialog->setPublicPosts(this->publicPostsCheckbox->isChecked()); this->hide(); // close() would kill the program if mainWindow was hidden this->deleteLater(); event->ignore(); } dianara-v1.3.2/translations/000755 000764 000764 00000000000 12612445313 015416 5ustar00janjan000000 000000 dianara-v1.3.2/translations/dianara_es.ts000644 000764 000764 00000647223 12614520443 020072 0ustar00janjan000000 000000 ASActivity Public Público %1 by %2 1=kind of object: note, comment, etc; 2=author's name %1 de %2 ASObject Note Noun, an object type Nota Article Noun, an object type Artículo Image Noun, an object type Imagen Audio Noun, an object type Audio Video Noun, an object type Vídeo File Noun, an object type Archivo Comment Noun, as in object type: a comment Comentario Group Noun, an object type Grupo Collection Noun, an object type Colección Other As in: other type of post Otro No detailed location No hay ubicación detallada Deleted on %1 Eliminado el %1 and one other y uno más and %1 others y %1 más ASPerson Hometown Ciudad AccountDialog Your Pump.io address: Tu dirección pump.io: Webfinger ID, like username@pumpserver.org ID Webfinger, tipo usuario@servidorpump.org Your address, as username@server Tu dirección, como usuario@servidor Get &Verifier Code Obtener código de &verificación Verifier code: Código de verificación: Enter or paste the verifier code provided by your Pump server here Introduce o pega aquí el codigo de verificación proporcionado por tu servidor Pump &Save Details &Guardar datos If the browser doesn't open automatically, copy this address manually Si el navegador web no se abre automáticamente, copia esta dirección manualmente Account Configuration Configuración de la cuenta First, enter your Webfinger ID, your pump.io address. Primero, introduce tu identificador Webfinger, tu dirección pump.io. Your address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. Tu dirección es como usuario@servidorpump.org, y puedes encontrarla en tu perfil, en la interfaz web. If your profile is at https://pump.example/yourname, then your address is yourname@pump.example Si tu perfil está en https://pump.ejemplo/tunombre, entonces tu dirección es tunombre@pump.ejemplo If you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. 1=link to website Si aún no tienes una cuenta, puedes registrar una en %1. Este enlace te llevará a un servidor público aleatorio. If you need help: %1 Si necesitas ayuda: %1 Pump.io User Guide Guía de usuario de Pump.io Your address, like username@pumpserver.org Tu dirección, como usuario@servidorpump.org After clicking this button, a web browser will open, requesting authorization for Dianara Cuando pulses este botón, se abrirá un navegador web, solicitando autorización para Dianara Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. Don't translate the VERIFIER word! Una vez que hayas autorizado a Dianara desde la interfaz web de tu servidor Pump, recibirás un código llamado VERIFIER. Cópialo y pégalo en el campo de abajo. Enter the verifier code provided by your Pump server here Introduce aquí el codigo de verificación proporcionado por tu servidor Pump Paste the verifier here Pega el verificador aquí &Authorize Application &Autorizar la aplicación &Cancel &Cancelar Your account is properly configured. Tu cuenta está configurada correctamente. Press Unlock if you wish to configure a different account. Pulsa Desbloquear si quieres configurar una cuenta diferente. &Unlock &Desbloquear A web browser will start now, where you can get the verifier code Ahora se iniciará un navegador web, donde podrás obtener el código de verificación Your Pump address is invalid Tu dirección Pump no es válida Verifier code is empty El código de verificación está vacío Dianara is authorized to access your data Dianara tiene autorización para acceder a tu cuenta AudienceSelector 'To' List Lista 'Para' 'CC' List Lista 'CC' 'Cc' List Lista 'Cc' &Add to Selected &Añadir a seleccionados All Contacts Todos los contactos Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. Selecciona gente de la lista de la izquierda. Puedes arrastrarlos con el ratón, hacer clic o doble clic en ellos, o seleccionarlos y usar el botón de abajo. Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. ON THE LEFT should change to ON THE RIGHT in RTL languages Selecciona gente de la lista de la izquierda. Puedes arrastrarlos con el ratón, hacer clic o doble clic en ellos, o seleccionarlos y usar el botón de abajo. Clear &List Borrar &lista &Done &Hecho &Cancel &Cancelar Selected People Gente seleccionada AvatarButton Open %1's profile in web browser Abrir el perfil de %1 en el navegador web Open your profile in web browser Abrir tu perfil en el navegador web Send message to %1 Enviar mensaje a %1 Browse messages Ver mensajes Stop following Dejar de seguir Follow Seguir Stop following? ¿Dejar de seguir? Are you sure you want to stop following %1? ¿Estás seguro de que quieres dejar de seguir a %1? &Yes, stop following &Sí, dejar de seguir &No &No ColorPicker Change Cambiar Choose a color Elige un color Comment Posted on %1 Publicado el %1 Modified on %1 Modificado el %1 Like or unlike this comment Decir que te gusta o que ya no te gusta este comentario Quote This is a verb, infinitive Citar Reply quoting this comment Responder citando este comentario Edit Editar Modify this comment Modificar este comentario Delete Eliminar Erase this comment Borrar este comentario Unlike Ya no me gusta Like Me gusta %1 like this comment Plural: %1=list of people like John, Jane, Smith A %1 les gusta este comentario %1 likes this comment Singular: %1=name of just 1 person A %1 le gusta este comentario WARNING: Delete comment? ADVERTENCIA: ¿Eliminar comentario? Are you sure you want to delete this comment? ¿Estás seguro de que quieres eliminar este comentario? &Yes, delete it &Sí, eliminarlo &No &No CommenterBlock You can press Control+Enter to send the comment with the keyboard Puedes pulsar Control+Enter para enviar el comentario con el teclado &Cancel &Cancelar Reload comments Actualizar comentarios Comment Infinitive verb Comentar Cancel Cancelar Press ESC to cancel the comment if there is no text Pulsa ESC para cancelar el comentario si no hay texto Check for comments Comprobar comentarios Show all %1 comments Mostrar los %1 comentarios Comments are not available Los comentarios no están disponibles Error: Already composing Error: Ya se está redactando You can't edit a comment at this time, because another comment is already being composed. No puedes editar un comentario en este momento, porque ya se está redactando otro comentario. Editing comment Editando comentario Posting comment failed. Try again. Ha fallado la publicación del comentario. Prueba de nuevo. Sending comment... Enviando comentario... Updating comment... Actualizando comentario... Comment is empty. El comentario está vacío. Composer Bold Negrita Italic Cursiva Make a link Hacer un enlace Click here or press Control+N to post a note... Haz clic aquí o pulsa Control+N para publicar una nota... Type a message here to post it Escribe un mensaje aquí para publicarlo Normal Normal Symbols Símbolos Formatting Formato Underline Subrayado Strikethrough Tachado Header Título List Lista Table Tabla Preformatted block Bloque preformateado Quote block Bloque de cita Insert an image from a web site Insertar una imagen desde un sitio web Insert line Insertar línea Insert as image? ¿Insertar como imagen? The link you are pasting seems to point to an image. Parece que el enlace que estás pegando apunta a una imagen. Insert as visible image Insertar como imagen visible Insert as link Insertar como enlace Table Size Tamaño de la tabla How many rows (height)? ¿Cuántas filas (altura)? How many columns (width)? ¿Cuántas columnas (ancho)? Type or paste a web address here. You could also select some text first, to turn it into a link. Escribe o pega una dirección web aquí. También puedes seleccionar texto antes, para convertirlo en un enlace. Type or paste the image address here. The link must point to the image file directly. Escribe o pega la dirección de la imagen aquí. El enlace ha de apuntar al archivo de imagen directamente. Text Formatting Options Opciones de formato de texto Paste Text Without Formatting Pegar texto sin formato Insert an image from a URL Insertar una imagen desde una URL Error: Invalid URL Error: URL no válida The address you entered (%1) is not valid. Image addresses should begin with http:// or https:// La dirección que has introducido (%1) no es válida. Las direcciones de imagenes han de empezar por http:// o https:// Cancel message? ¿Cancelar el mensaje? Are you sure you want to cancel this message? ¿Seguro que quieres cancelar este mensaje? &Yes, cancel it &Sí, cancelarlo &No &No Insert a link Insertar un enlace &Format Button for text formatting and related options &Formato Type a comment here Escribe un comentario aquí Make a link from selected text Hacer un link con el texto seleccionado Type or paste a web address here. The selected text (%1) will be converted to a link. Escribe o pega una dirección web aquí. El texto seleccionado (%1) será convertido en un enlace. ConfigDialog minutes minutos Top Parte superior Bottom Parte inferior Left side Lado izquierdo Right side Lado derecho Program Configuration Configuración del programa Timeline &update interval Intervalo de &actualización de la línea temporal &Tabs position Posición de las &pestañas &Movable tabs Pes&tañas movibles Minor Feeds Líneas temporales menores posts Goes after a number, as: 25 posts mensajes &Posts per page, main timeline &Mensajes por página, línea temporal principal posts This goes after a number, like: 10 posts mensajes Posts per page, &other timelines Mensajes por página, &otras líneas temporales Public posts as &default Mensajes públicos de manera &predefinida Pro&xy Settings Ajustes de pro&xy Network configuration Configuración de red Set Up F&ilters Configurar f&iltros Filtering rules Normas de filtrado Highlighted activities, except mine Actividades destacadas, excepto las mías Any highlighted activity Cualquier actividad destacada Always Siempre Never Nunca Show snippets in minor feed Mostrar fragmentos en la línea temporal menor characters This is a suffix, after a number caracteres Snippet limit Límite de los fragmentos Post Titles Títulos de los mensajes Post Contents Contenido de los mensajes Comments Comentarios Minor Feed Línea temporal menor You are among the recipients of the activity, such as a comment addressed to you. Estás entre los destinatarios de la actividad, como por ejemplo un comentario dirigido a ti. Used also when highlighting posts addressed to you in the timelines. Usado también cuando se destacan mensajes dirigidos a ti en las líneas temporales. The activity is in reply to something done by you, such as a comment posted in reply to one of your notes. La actividad es en respuesta a alguna cosa hecha por ti, como un comentario publicado en respuesta a una de tus notas. You are the object of the activity, such as someone adding you to a list. Eres el objeto de la actividad, como por ejemplo cuando alguien te añade a una lista. The activity is related to one of your objects, such as someone liking one of your posts. La actividad está relacionada con uno de tus objetos, como por ejemplo cuando a alguien le gusta uno de tus mensajes. Used also when highlighting your own posts in the timelines. Usado también cuando se destacan tus propios mensajes en las líneas temporales. Item highlighted due to filtering rules. Elemento destacado por normas de filtrado. Item is new. El elemento es nuevo. Show snippets in minor feeds Mostrar fragmentos en las líneas temporales menores Hide duplicated posts Ocultar mensajes duplicados Only for embedded images Solo para imágenes incrustadas... Avatar size Tamaño de los avatares Show extended share information Mostrar información adicional en los compartidos Show extra information Mostrar información extra Highlight post author's comments Destacar comentarios del autor del mensaje Highlight your own comments Destacar tus propios comentarios Ignore SSL errors in images Ignorar errores de SSL en las imágenes Show character counter Mostrar contador de caracteres Don't inform followers when following someone No informar a los seguidores al seguir a alguien Don't inform followers when handling lists No informar a los seguidores al manipular listas Only inform the author when liking posts and comments Informar sólo al autor de los "Me gusta" en mensajes y comentarios As system notifications Como notificaciones del sistema Using own notifications Usando notificaciones propias Don't show notifications No mostrar notificaciones Notification Style Estilo de las notificaciones Notify when receiving: Notificar cuando se reciban: New posts Mensajes nuevos Highlighted posts Mensajes destacados New activities in minor feed Nuevas actividades en la línea temporal menor Highlighted activities in minor feed Actividades destacadas en la línea temporal menor Default el icono Predeterminado System iconset, if available Icono del sistema, si está disponible Show your current avatar Mostrar tu avatar actual Custom icon Icono personalizado System Tray Icon &Type &Tipo de icono en la bandeja del sistema Hide window on startup Ocultar ventana al iniciar General Options Opciones generales Fonts Tipos de letra Colors Colores Timelines Líneas temporales Posts Mensajes Composer Editor Privacy Privacidad Notifications Notificaciones System Tray Bandeja del sistema S&elect... S&eleccionar... Custom &Icon &Icono personalizado Select custom icon Selecciona icono personalizado Dianara stores data in this folder: Dianara guarda datos en esta carpeta: Left side tabs on left side/west; RTL not affected Lado izquierdo Right side tabs on right side/east; RTL not affected Lado derecho Show information for deleted posts Mostrar información de los mensajes eliminados Jump to new posts line on update Saltar a la línea de nuevos mensajes al actualizar Only for images inserted from web sites. Sólo para imágenes insertadas desde sitios web. Use with care. Usar con cuidado. Use attachment filename as initial post title Usar nombre del adjunto como título inicial del mensaje Inform only the author when liking things Informar sólo al autor de los "Me gusta" &Save Configuration &Guardar configuración &Cancel &Cancelar This is a system notification Esto es una notificación del sistema System notifications are not available! ¡Las notificaciones del sistema no están disponibles! Own notifications will be used. Se usarán las notificaciones propias. This is a basic notification Esto es una notificación básica Image files Archivos de imagen All files Todos los archivos Invalid image Imagen no válida The selected image is not valid. La imagen seleccionada no es válida. ContactCard Hometown Ciudad Joined: %1 Se unió: %1 Updated: %1 Actualizado: %1 Bio for %1 Abbreviation for Biography, but you can use the full word; %1=contact name Bio de %1 This user doesn't have a biography Este usuario no tiene una biografía No biography for %1 %1=contact name No hay biografía para %1 Open Profile in Web Browser Abrir el perfil en el navegador web Send Message Enviar mensaje Browse Messages Ver mensajes In Lists... En listas... User Options Opciones de usuario Follow Seguir Stop Following Dejar de seguir Stop following? ¿Dejar de seguir? Are you sure you want to stop following %1? ¿Estás seguro de que quieres dejar de seguir a %1? &Yes, stop following &Sí, dejar de seguir &No &No ContactList Type a partial name or ID to find a contact... Escribe una parte de un nombre o ID para encontrar un contacto... F&ull List Lista c&ompleta ContactManager username@server.org or https://server.org/username usuario@servidor.org o https://servidor.org/usuario &Enter address to follow: &Introduce dirección para seguir: &Follow &Seguir Reload Followers Actualizar Seguidores Reload Following Actualizar Siguiendo Export Followers Exportar Seguidores Export Following Exportar Siguiendo Reload Lists Actualizar listas &Neighbors V&ecinos Site &Users "del sitio", "del servidor"? &Usuarios locales Follo&wers Se&guidores Followin&g Siguien&do &Lists &Listas Export list of 'following' to a file Exportar lista de 'siguiendo' a un archivo Export list of 'followers' to a file Exportar lista de 'seguidores' a un archivo DownloadWidget Download Descargar Save the attached file to your folders Guardar el archivo adjunto en tus carpetas Cancel Cancelar Save File As... Guardar archivo como... All files Todos los archivos Abort download? ¿Interrumpir la descarga? Do you want to stop downloading the attached file? ¿Quieres detener la descarga del archivo adjunto? &Yes, stop &Sí, detener &No, continue &No, continuar Download aborted Descarga interrumpida Download completed Descarga completada Download failed Ha fallado la descarga Downloading %1 KiB... Descargando %1 KiB... %1 KiB downloaded hmmm FIXME %1 KiB descargados EmailChanger Change E-mail Address Cambiar dirección de e-mail Change Cambiar &Cancel &Cancelar E-mail Address: Dirección de e-mail: Again: Otra vez: Repeat: Repite: Your Password: Tu contraseña: E-mail addresses don't match! ¡Las direcciones de e-mail no coinciden! Password is empty! ¡La contraseña está vacía! FilterEditor Filter Editor Editor de filtros Application Aplicación %1 if %2 contains: %3 This explains a filter rule, like: Hide if Author ID contains JohnDoe %1 si %2 contiene: %3 Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. Aquí puedes establecer algunas normas para ocultar o destacar cosas. Puedes filtrar por contenido, autor o aplicación. Por ejemplo, puedes filtrar mensajes publicados por la aplicación Open Farm Game, o que contienen la palabra NSFW en el mensaje. También podrías destacar mensajes que contienen tu nombre. Hide Ocultar Highlight Destacar Post Contents Contenido de la publicación Author ID ID del autor Activity Description Descripción de la actividad Keywords... Palabras clave... &Add Filter &Añadir filtro Filters in use Filtros en uso &Remove Selected Filter &Eliminar filtro seleccionado &Save Filters &Guardar filtros &Cancel &Cancelar if si contains contiene &New Filter &Nuevo filtro C&urrent Filters Filtros &actuales FirstRunWizard First Run Wizard Asistente de primera ejecución Welcome Wizard Asistente de bienvenida Welcome to Dianara! ¡Bienvenido a Dianara! This wizard will help you get started. Este asistente te ayudará a empezar. You can access this window again at any time from the Help menu. Puedes volver a acceder a esta ventana en cualquier momento desde el menú de Ayuda. The first step is setting up your account, by using the following button: El primer paso es configurar tu cuenta, usando el siguiente botón: Configure your &account Configur&a tu cuenta Once you have configured your account, it's recommended that you edit your profile and add an avatar and some other information, if you haven't done so already. Una vez que hayas configurado tu cuenta, es recomendable que edites tu perfil y añadas un avatar y algo más de información, si no lo has hecho ya. &Edit your profile &Edita tu perfil By default, Dianara will post only to your followers, but it's recommended that you post to Public, at least sometimes. De manera predefinida, Dianara publicará sólo para tus seguidores, pero es recomendable que publiques para Público, al menos a veces. Post to &Public by default Publicar para &Público de manera predefinida Open general program &help window Abrir la ventana de ay&uda general del programa &Show this again next time Dianara starts &Mostrar de nuevo la próxima vez que se inicie Dianara &Close &Cerrar FontPicker Change Cambiar Choose a font Elige un tipo de letra HelpWidget Basic Help Ayuda básica Getting started Empezando The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. La primera vez que inicies Dianara, deberías ver la ventana de Configuración de la cuenta. Allí, introduce tu dirección de Pump.io como nombre@servidor y pulsa el botón Obtener código de verificación. Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. A continuación, tu navegador web habitual debería cargar la página de autorización en tu servidor Pump.io. Allí, tendrás que copiar el código 'VERIFIER' completo, y pegarlo en el segundo campo de Dianara. Entonces pulsa 'Autorizar la aplicación', y una vez que se confirme, pulsa 'Guardar datos'. At this point, your profile, contact lists and timelines will be loaded. En este momento, se cargará tu perfil, las listas de contactos y las líneas temporales. You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. Deberías echar un vistazo a la ventana 'Configuración del programa', en el menú Configuración - Configurar Dianara. Allí encontrarás varias opciones interesantes. Settings Configuración You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. Puedes ajustar varias cosas a tu gusto en la configuración, como el intervalo de tiempo entre actualizaciones de la línea temporal, cuantos mensajes por página quieres, colores para destacar mensajes, notificaciones o qué aspecto tendrá el icono de la bandeja del sistema. Timelines Líneas temporales Contents Índice Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. Recuerda que hay muchos lugares en Dianara donde puedes obtener más información manteniendo el ratón sobre algún texto o botón, y esperando a que aparezca la descripción emergente (tooltip). Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. Aquí, también puedes activar la opción para publicar siempre tus mensajes como públicos de forma predefinida. Siempre puedes cambiar esto en el momento de publicar. The main timeline, where you'll see all the stuff posted or shared by the people you follow. La línea temporal principal, donde verás todo lo que ha publicado o compartido la gente a la que sigues. Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. Línea temporal de mensajes, donde verás mensajes enviados a ti específicamente. Estos mensajes pueden haber sido enviados también a otras personas. Activity timeline, where you'll see your own posts, or posts shared by you. Línea temporal de actividad, donde verás tus propios mensajes, o mensajes que has compartido. Favorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. Línea temporal de favoritos, donde verás los mensajes que te han gustado. Esto puede usarse como un sistema de marcadores. The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. La quinta línea temporal es la línea temporal menor, también conocida como el "Mientras tanto". Es visible en el lado izquierdo, aunque se puede esconder. Aquí verás actividades secundarias hechas por todo el mundo, como acciones de comentar, marcar mensajes con "Me gusta" o seguir a gente. These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. Estas actividades pueden tener un botón "+". Púlsalo para abrir el mensaje al que hacen referencia. Además, como en muchos otros lugares, puedes mantener el ratón encima para ver información relevante en la descripción emergente (tooltip). Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. Dentro de la pestaña 'Vecinos' verás algunos recursos para encontrar gente, y tendrás la opción de ver los últimos usuarios registrados en tu servidor, directamente. Dianara offers a D-Bus interface that allows some control from other applications. Dianara ofrece una interfaz D-Bus que permite un cierto control desde otras aplicaciones. The interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. La interfaz se encuentra en %1, y puedes acceder a ella con herramientas como %2 o %3. Ofrece métodos como %4 y %5. Posting Publicando If you're new to Pump.io, take a look at this guide: Si es la primera vez que usas Pump.io, echa un vistazo a esta guía: New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. Los mensajes nuevos aparecen destacados en un color diferente. Puedes marcarlos como leídos haciendo clic en cualquier parte vacía del mensaje. You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. Puedes publicar notas haciendo clic en el campo de texto de la parte superior de la ventana o pulsando Control+N. Añadir un título al mensaje es opcional, pero muy recomendable, ya que ayudará a identificar mejor las referencias a tu mensaje en la línea temporal menor, notificaciones por e-mail, etc. It is possible to attach images, audio, video, and general files, like PDF documents, to your post. Es posible adjuntar imágenes, audio, video y archivos generales, como documentos PDF, a tu mensaje. You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. Puedes usar el botón Formato para añadir formato al texto, como negrita o cursiva. Algunas de estas opciones requieren que se seleccione el texto antes de usarlas. You can select who will see your post by using the To and CC buttons. Puedes seleccionar quien verá tu mensaje usando los botones Para y CC. If you add a specific person to the 'To' list, they will receive your message in their direct messages tab. Si añades una persona específica a la lista 'Para', recibirá tu mensaje en su pestaña de mensajes directos. You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. Puedes hacer mensajes privados añadiendo gente específica a estas listas, y desmarcando las opciones Público y Seguidores. Managing contacts Gestionando los contactos You can see the lists of people you follow, and who follow you from the Contacts tab. Puedes ver las listas de gente a la que sigues, y que te sigue, en la pestaña Contactos. There, you can also manage person lists, used mainly to send posts to specific groups of people. Allí puedes gestionar también las listas de personas, que se utilizan principalmente para enviar mensajes a grupos de gente específicos. You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. Puedes hacer clic en cualquier avatar en los mensajes, los comentarios, y la columna "Mientras tanto", y verás un menú con varias opciones, una de las cuales es seguir o dejar de seguir a esta persona. Keyboard controls Control por teclado Pump.io User Guide Guía de usuario de Pump.io There are seven timelines: Hay siete líneas temporales: The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). La sexta y séptima líneas temporales también son líneas temporales menores, parecidas al "Mientras tanto", pero contienen únicamente actividades dirigidas a ti (Menciones) y actividades hechas por ti (Acciones). You can select who will see your post by using the To and Cc buttons. Puedes seleccionar quien verá tu mensaje usando los botones 'Para' y 'Cc'. You can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. También puedes teclear '@' y los primeros caracteres del nombre de un contacto para mostrar un menú emergente con opciones que coincidan. Choose one with the arrows and press Enter to complete the name. This will add that person to the recipients list. Elige una con las teclas de cursor y pulsa Enter para completar el nombre. Esto añadirá a esta persona a la lista de destinatarios. You can also send a direct message (initially private) to that contact from this menu. También puedes enviar un mensaje directo (inicialmente privado) a este contacto desde este menú. You can find a list with some Pump.io users and other information here: Puedes encontrar una lista con algunos usuarios de Pump.io y otros datos aquí: Users by language Usuarios por idioma The most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. Las acciones más comunes que se encuentran en los menús tienen atajos de teclado escritos al lado, como F5 o Control+N. Besides that, you can use: Aparte de eso, puedes usar: Control+Up/Down/PgUp/PgDown/Home/End to move around the timeline. Control+Arriba/Abajo/RePag/AvPag/Inicio/Fin para moverte por la línea temporal. Control+Left/Right to jump one page in the timeline. Control+Izquierda/Derecha para pasar una página en la línea temporal. Control+G to go to any page in the timeline directly. Control+G para ir a cualquier página de la línea temporal directamente. Control+1/2/3 to switch between the minor feeds. Control+1/2/3 para cambiar entre las líneas temporales menores. Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. Control+Enter para publicar, cuando hayas acabado de redactar una nota o un comentario. Si la nota está vacía, la puedes cancelar pulsando ESC. Command line options Opciones de línea de comandos The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. LEFT SIDE should change to RIGHT SIDE on RTL languages La quinta línea temporal es la línea temporal menor, también conocida como el "Mientras tanto". Es visible en el lado izquierdo, aunque se puede esconder. Aquí verás actividades secundarias hechas por todo el mundo, como acciones de comentar, marcar mensajes con "Me gusta" o seguir a gente. Choose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. Elige una con las teclas de cursor y pulsa Enter para completar el nombre. Esto añadirá a esta persona a la lista de destinatarios. There is a text field at the top, where you can directly enter addresses of new contacts to follow them. Hay un campo de texto en la parte superior, donde puedes introducir directamente direcciones de contactos nuevos para seguirles. While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. Mientras redactas una nota, pulsa Enter para pasar del título al cuerpo del mensaje. Además, pulsando la flecha arriba cuando te encuentres al principio del mensaje, vuelve al título. Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. Control+Enter para acabar de crear una lista de destinatarios para un mensaje, en las listas 'Para' o 'Cc'. You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. Puedes usar el parámetro --config para ejecutar el programa con una configuración diferente. Esto puede ser útil para usar dos o más cuentas diferentes. Incluso puedes ejecutar dos instancias de Dianara a la vez. Use the --debug parameter to have extra information in your terminal window, about what the program is doing. Usa el parámetro --debug para tener información adicional en la ventana de la terminal, sobre lo que está haciendo el programa. If your server does not support HTTPS, you can use the --nohttps parameter. Si tu servidor no tiene soporte HTTPS, puedes utilizar el parámetro --nohttps. If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. Si usas una configuración alternativa, con algo como '--config otraconf', entonces la interfaz estará en org.nongnu.dianara_otraconf. &Close &Cerrar ImageViewer Image Imagen Resolution and file size Resolución y tamaño del archivo ESC to close, secondary-click for options ESC para cerrar, clic secundario para opciones &Save As... &Guardar como... &Restart Animation &Reiniciar animación &Close &Cerrar Save Image... Guardar imagen... Close Viewer Cerrar visor Save Image As... Guardar imagen como... Image files Archivos de imagen All files Todos los archivos Error saving image Error guardando la imagen There was a problem while saving %1. Filename should end in .jpg or .png extensions. Ha habido un problema al guardar %1. El nombre del archivo debería acabar con la extensión .jpg o .png. ListsManager Name Nombre Members Miembros Add Mem&ber Añadir miem&bro &Remove Member &Eliminar miembro &Delete Selected List &Borrar lista seleccionada Add New &List Añadir nueva &lista Create L&ist Crear l&ista &Add to List &Añadir a lista Are you sure you want to delete %1? 1=Name of a person list ¿Estás seguro de que quieres eliminar %1? Remove person from list? ¿Quitar persona de la lista? Are you sure you want to remove %1 from the %2 list? 1=Name of a person, 2=name of a list ¿Estás seguro de que quieres quitar a %1 de la lista %2? &Yes &Sí Type a name for the new list... Escribe un nombre para la nueva lista... Type an optional description here Escribe una descripción opcional aquí WARNING: Delete list? ADVERTENCIA: ¿Eliminar lista? &Yes, delete it &Sí, eliminarla &No &No LogViewer Log Registro Clear &Log Borrar el &registro &Close &Cerrar MainWindow &Messages &Mensajes &Contacts &Contactos &Quit &Salir &Session &Sesión Meanwhile... Mientras tanto... Side &Panel Panel &lateral Status &Bar &Barra de estado &Timeline Línea &temporal &Activity &Actividad Initializing... Inicializando... Your account is not configured yet. Tu cuenta no está configurada todavía. Dianara started. Dianara iniciado. Minor activities done by everyone, such as replying to posts Actividades menores hechas por todo el mundo, tales como respuestas a mensajes Minor activities addressed to you Actividades menores dirigidas a ti Actions Acciones Minor activities done by you Actividades menores hechas por ti The people you follow, the ones who follow you, and your person lists La gente a la que sigues, la que te sigue, y tus listas de personas Running with Qt v%1. Funcionando con Qt v%1. &Update Timeline Act&ualizar línea temporal Update &Messages Actualizar &mensajes Update &Activity Actualizar &actividad Update Fa&vorites Actualizar &favoritos Update Mentions Actualizar menciones Update Actions Actualizar acciones Auto-update &Timelines Auto-actualizar líneas &temporales Mark All as Read Marcar todo como leído &Post a Note &Publicar una nota &View &Ver &Toolbar &Barra de herramientas Full &Screen &Pantalla completa &Log &Registro S&ettings &Configuración Edit &Profile Editar &perfil &Account &Cuenta &Configure Dianara &Configurar Dianara &Help Ay&uda Basic &Help &Ayuda básica Visit &Website Visitar sitio &web Report a &Bug Informar de un &fallo Pump.io User &Guide &Guía de usuario de Pump.io List of Some Pump.io &Users Lista de algunos &usuarios de Pump.io Pump.io &Network Status Website Web del estado de la &red Pump.io About &Dianara Acerca de &Dianara Toolbar Barra de herramientas Open the log viewer Abrir el visor del registro Auto-updating enabled Auto-actualizaciones activadas Auto-updating disabled Auto-actualizaciones desactivadas Proxy password required Contraseña de proxy necesaria You have configured a proxy server with authentication, but the password is not set. Has configurado un servidor proxy con autenticación, pero la contraseña no está definida. Enter the password for your proxy server: Introduce la contraseña para tu servidor proxy: Your biography is empty Tu biografía está vacía Click to edit your profile Haz clic para editar tu perfil Starting automatic update of timelines, once every %1 minutes. Iniciando actualización automática de líneas temporales, una vez cada %1 minutos. Stopping automatic update of timelines. Deteniendo actualización automática de líneas temporales. Received %1 older posts in '%2'. %1 is a number, %2 = name of a timeline Se han recibido %1 mensajes anteriores en '%2'. 1 more pending to receive. singular, one post 1 más pendiente de recibir. %1 more pending to receive. plural, several posts %1 más pendientes de recibir. Also: Además: Last update: %1 Última actualización: %1 1 more pending to receive. singular, 1 activity 1 más pendiente de recibir. %1 more pending to receive. plural, several activities %1 más pendientes de recibir. '%1' updated. %1 is the name of a feed '%1' actualizada. 1 pending to receive. singular, one post 1 pendiente de recibir. %1 pending to receive. plural, several posts %1 pendientes de recibir. 1 highlighted. singular, refers to a post 1 destacado. %1 highlighted. plural, refers to posts %1 destacados. 1 filtered out. singular, refers to a post 1 filtrado. %1 filtered out. plural, refers to posts %1 filtrados. 1 deleted. singular, refers to a post 1 eliminado. %1 deleted. plural, refers to posts %1 eliminados. No new posts. No hay mensajes nuevos. Mentions Menciones There is 1 new activity. Hay 1 actividad nueva. There are %1 new activities. Hay %1 actividades nuevas. 1 pending to receive. singular, 1 activity 1 pendent de rebre. %1 pending to receive. plural, several activities %1 pendientes de recibir. 1 highlighted. singular, refers to an activity 1 destacada. %1 highlighted. plural, refers to activities %1 destacadas. 1 filtered out. singular, refers to one activity 1 filtrada. %1 filtered out. plural, several activities %1 filtradas. No new activities. No hay actividades nuevas. Error storing image! ¡Error almacenando la imagen! %1 bytes %1 bytes Link to: %1 Enlace a: %1 Marking everything as read... Marcando todo como leído... With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. Con Dianara puedes ver tus líneas temporales, crear nuevos mensajes, subir fotos y multimedia, interactuar con los mensajes, gestionar tus contactos y seguir gente nueva. English translation by JanKusanagi. TRANSLATORS: Change this with your language and name. If there was another translator before you, add your name after theirs ;) Traducción al castellano por JanKusanagi. &Hide Window &Ocultar ventana &Show Window &Mostrar ventana Closing due to environment shutting down... meh... Cerrando debido a que el entorno se está apagando... Quit? ¿Salir? You are composing a note or a comment. Estás redactando una nota o un comentario. Do you really want to close Dianara? ¿Realmente quieres cerrar Dianara? &Yes, close the program &Sí, cerrar el programa &No &No Shutting down Dianara... Cerrando Dianara... System tray icon is not available. El icono de la bandeja del sistema no está disponible. Dianara cannot be hidden in the system tray. Dianara no se puede ocultar en la bandeja del sistema. Do you want to close the program completely? ¿Quieres cerrar el programa completamente? There is 1 new post. Hay 1 mensaje nuevo. There are %1 new posts. Hay %1 mensajes nuevos. Timeline updated. Línea temporal actualizada. Timeline updated at %1. Línea temporal actualizada a las %1. Total posts: %1 Total de mensajes: %1 Your Pump.io account is not configured Tu cuenta Pump.io no está configurada Received %1 older items in '%2'. %1 is a number, %2 = name of feed Se han recibido %1 elementos anteriores en '%2'. Dianara is a pump.io social networking client. Dianara es un cliente de red social para pump.io. Thanks to all the testers, translators and packagers, who help make Dianara better! ¡Gracias a todos los 'testers', traductores y empaquetadores, que ayudan a hacer Dianara mejor! Dianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) Dianara está licenciado bajo la licencia GNU GPL, y utiliza algunos iconos Oxygen: http://www.oxygen-icons.org/ (Licencia LGPL) The main timeline La línea temporal principal Press F1 for help Pulsa F1 para ver la ayuda Click here to configure your account Haz clic aquí para configurar tu cuenta Update %1 Actualizar %1 Show Welcome Wizard Mostrar el asistente de bienvenida Your own posts Tus propios mensajes Your favorited posts Los mensajes que te gustan Messages sent explicitly to you Mensajes enviados explícitamente a ti Update Minor &Feed meh... Actualizar línea temporal meno&r &Filters and Highlighting hmmm &Filtros y destacados Some Pump.io &Tips Algunos &consejos sobre Pump.io Favor&ites &Favoritos Received %1 older activities in '%2'. %1 is a number, %2 = name of feed Se han recibido %1 actividades anteriores en '%2'. Minor feed updated at %1. Línea temporal menor actualizada a las %1. Minor feed updated. Línea temporal menor actualizada. About Dianara Acerca de Dianara MinorFeed Older Activities Actividades anteriores Get previous minor activities Obtener actividades menores anteriores There are no activities to show yet. Aún no hay actividades para mostrar. Get %1 newer As in: Get 3 newer (activities) Recibir %1 más nuevas MinorFeedItem Using %1 Application used to generate this activity Usando %1 To: %1 1=people to whom this activity was sent Para: %1 Cc: %1 1=people to whom this activity was sent as CC Cc: %1 CC: %1 1=people to whom this activity was sent as CC CC: %1 Open referenced post Abrir el mensaje referenciado MiscHelpers bytes bytes PageSelector Jump to page Saltar a página Page number: Número de página: &First As in: first page &Primera &Last As in: last page Últim&a Newer As in: newer pages Más nuevas Older As in: older pages Más antiguas &First &Primera &Last Ultim&a Newer Más nuevas Older Más antiguas &Go &Ir Go to &last page Ir a la última &página &Cancel &Cancelar PeopleWidget &Search: &Buscar: Enter a name here to search for it Escribe aquí un nombre para buscarlo Add a contact to a list Añadir un contacto a una lista &Cancel &Cancelar Post Like this post Decir que te gusta este mensaje Like Me gusta Shared on %1 Compartido el %1 &Close &Cerrar In En To Para CC CC Using %1 1=Program used for posting or sharing Usando %1 Parent As in 'Open the parent post'. Try to use the shortest word! Padre Open the parent post, to which this one replies Abrir el mensaje padre, al que éste responde Modify this post Modificar este mensaje Join Group Unirse al grupo %1 members in the group %1 miembros en el grupo 1 like Le gusta a 1 1 comment 1 comentario Shared %1 times Compartido %1 veces Share Compartir Click to download the attachment Haz clic para descargar el archivo adjunto Post Noun, not verb Mensaje Type As in: type of object Tipo Modified on %1 Modificado el %1 Edit Editar Image is animated. Click on it to play. La imagen es animada. Haz clic para reproducirla. Loading image... Cargando imagen... Attached file Archivo adjunto %1 likes Les gusta a %1 %1 comments %1 comentarios Delete Eliminar Via %1 Meh... A través de %1 Edited: %1 Editado: %1 Posted on %1 1=Date Publicado el %1 If you select some text, it will be quoted. Si seleccionas parte del texto, será citado. Unshare Dejar de compartir Unshare this post Dejar de compartir este mensaje Open post in web browser Abrir el mensaje en el navegador web Cc Cc Copy post link to clipboard Copiar el enlace del mensaje al portapapeles Normalize text colors Normalizar colores del texto Comment verb, for the comment button Comentar Reply to this post. Responder a este mensaje. Share this post with your contacts Compartir este mensaje con tus contactos Erase this post Borrar este mensaje Couldn't load image! ¡No se ha podido cargar la imagen! Attached Audio Audio adjunto Attached Video Vídeo adjunto Attached File Archivo adjunto %1 likes this One person Le gusta a %1 %1 like this More than one person Les gusta a %1 %1 shared this %1 = One person name %1 ha compartido esto %1 shared this %1 = Names for more than one person %1 han compartido esto Shared once Compartido una vez You like this Te gusta esto Unlike Ya no me gusta Share post? ¿Compartir mensaje? Do you want to share %1's post? ¿Quieres compartir el mensaje de %1? &Yes, share it &Sí, compartirlo &No &No Unshare post? ¿Dejar de compartir el mensaje? Do you want to unshare %1's post? ¿Quieres dejar de compartir el mensaje de %1? &Yes, unshare it &Sí,dejar de compartirlo WARNING: Delete post? ADVERTENCIA: ¿Eliminar mensaje? Are you sure you want to delete this post? ¿Estás seguro de que quieres eliminar este mensaje? &Yes, delete it &Sí, eliminarlo Click the image to see it in full size Haz clic en la imagen para verla en tamaño completo ProfileEditor Profile Editor Editor de perfil This is your Pump address Esta es tu dirección Pump This is the e-mail address associated with your account, for things such as notifications and password recovery Esta es la dirección de e-mail asociada con tu cuenta, para cosas como notificaciones y recuperación de la contraseña Change &E-mail... Cambiar &e-mail... Change &Avatar... Cambiar &avatar... This is your visible name Este es tu nombre visible &Save Profile &Guardar perfil &Cancel &Cancelar Webfinger ID ID Webfinger E-mail E-mail Avatar Avatar Full &Name &Nombre completo &Hometown Ciuda&d &Bio &Bio Not set In reference to the e-mail not being set for the account Sin definir Select avatar image Selecciona imagen de avatar Image files Archivos de imagen All files Todos los archivos Invalid image Imagen no válida The selected image is not valid. La imagen seleccionada no es válida. ProxyDialog Proxy Configuration Configuración de proxy Do not use a proxy No utilizar un proxy Your proxy username Tu nombre de usuario para el proxy Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. Nota: La contraseña no se guarda de forma segura. Si lo deseas, puedes dejar el campo vacío, y se te pedirá la contraseña al iniciar. &Save &Guardar &Cancel &Cancelar Proxy &Type &Tipo de proxy &Hostname &Servidor &Port &Puerto Use &Authentication Usar &autenticación &User &Usuario Pass&word Con&traseña Publisher Public Público Followers Seguidores Picture Foto Audio Audio Video Vídeo Ad&d... &Añadir... Upload media, like pictures or videos Subir multimedia, como fotos o vídeos Select Picture... Seleccionar foto... Title Título Find the picture in your folders Encontrar la foto en tus carpetas People... Personas... Select who will see this post Selecciona quien verá este mensaje To... Para... Lists Listas CC... CC... Select who will get a copy of this post Selecciona quien recibirá una copia de este mensaje Other as in other kinds of files Otros Cancel Cancelar Cancel the post Cancelar el mensaje Picture not set Foto no seleccionada Select Audio File... Seleccionar archivo de audio... Find the audio file in your folders Encontrar el archivo de audio en tus carpetas Audio file not set Archivo de audio no seleccionado Select Video... Seleccionar video... Find the video in your folders Encontrar el vídeo en tus carpetas Video not set Vídeo no seleccionado Select File... Seleccionar archivo... Find the file in your folders Encontrar el archivo en tus carpetas File not set Archivo no seleccionado Error: Already composing Error: Ya se está redactando You can't edit a post at this time, because a post is already being composed. No puedes editar un mensaje en este momento, porque ya se está redactando un mensaje. Update Actualizar Editing post Editando mensaje You can't create a message for %1 at this time, because a post is already being composed. No puedes crear un mensaje para %1 en este momento, porque ya se está redactando un mensaje. Posting failed. Try again. Ha fallado la publicación. Prueba de nuevo. Warning: You have no followers yet Advertencia: Aún no tienes seguidores You're trying to post to your followers only, but you don't have any followers yet. Estás intentando publicar sólo para tus seguidores, pero no tienes ningún seguidor todavía. If you post like this, no one will be able to see your message. Si publicas de esta manera, nadie podrá ver tu mensaje. &Cancel, go back to the post &Cancelar, volver al mensaje Updating... Actualizando... Post is empty. El mensaje está vacío. File not selected. No se ha seleccionado un archivo. Select one image Selecciona una imagen Image files Archivos de imagen Select one file Selecciona un archivo Invalid file Archivo no válido The file type cannot be detected. El tipo de archivo no se puede detectar. All files Todos los archivos Since you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. Como lo que estás subiendo es una imagen, podrías reducirla un poco o guardarla en un formato más comprimido, como JPG. Dianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. Actualmente, Dianara limita las subidas de archivos a 10 MiB por mensaje, para evitar posibles problemas de almacenamiento, o de red, en los servidores. This is a temporary measure, since the servers cannot set their own limits yet. Esto es una medida temporal, ya que los servidores no pueden establecer sus propios límites todavía. File is too big El archivo es demasiado grande Add a brief title for the post here (recommended) Añade un título breve para el mensaje aquí (recomendado) Do you want to make the post public instead of followers-only? ¿Quieres hacer el mensaje público en lugar de sólo para seguidores? &Yes, make it public &Sí, hacerlo público Sorry for the inconvenience. Lamentamos las molestias. Resolution Resolución Type Tipo Size Tamaño %1 KiB of %2 KiB uploaded %1 KiB de %2 KiB subidos Invalid image Imagen no válida Setting a title helps make the Meanwhile feed more informative Añadir un título ayuda a hacer el contenido del "Mientras tanto" más informativo Add a brief title for the post (recommended) Añade un título breve para el mensaje (recomendado) Remove Quitar? Eliminar? Quitar Cancel the attachment, and go back to a regular note Cancelar el adjunto y volver a una nota normal Cc... Cc... Post verb Publicar Note started from another application. Nota empezada desde otra aplicación. Ignoring new note request from another application. Ignorando petición de nueva nota desde otra aplicación. &No, post to my followers only &No, publicar sólo para mis seguidores The image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. No se puede detectar el formato de la imagen. La extensión podría estar equivocada, como una imagen GIF renombrada a imagen.jpg o similar. Select one audio file Selecciona un archivo de audio Audio files Archivos de audio Invalid audio file Archivo de audio no válido The audio format cannot be detected. El formato de audio no se puede detectar. Select one video file Selecciona un archivo de vídeo Video files Archivos de vídeo Invalid video file Archivo de vídeo no válido The video format cannot be detected. El formato de vídeo no se puede detectar. Posting... Publicando... Hit Control+Enter to post with the keyboard Pulsa Control+Enter para publicar con el teclado PumpController Getting likes... meh... Recibiendo "likes"... Getting comments... Recibiendo comentarios... Getting minor feed... meh... Recibiendo línea temporal menor... Error connecting to %1 Error conectando a %1 Unhandled HTTP error code %1 Código de error HTTP no gestionado: %1 Post published successfully. Mensaje publicado correctamente. Comment posted successfully. Comentario publicado correctamente. Minor feed received. meh Línea temporal menor recibida. Following %1 (%2) successfully. %1 is a person's name, %2 is the ID Siguiendo a %1 (%2) correctamente. Stopped following %1 (%2) successfully. %1 is a person's name, %2 is the ID Se ha dejado de seguir a %1 (%2) correctamente. List of 'following' completely received. Lista de 'siguiendo' completamente recibida. Partial list of 'following' received. Parte de la lista de 'siguiendo' recibida. List of 'followers' completely received. Lista de 'seguidores' completamente recibida. Partial list of 'followers' received. Parte de la lista de 'seguidores' recibida. List of %1 users received. %1 is a server name Lista de usuarios de %1 recibida. Person list '%1' created successfully. Lista de personas '%1' creada correctamente. Person list received. Lista de personas recibida. File uploaded successfully. Posting message... Archivo subido correctamente. Publicando mensaje... Timeline received. Updating post list... Línea temporal recibida. Actualizando lista de mensajes... Authorized to use account %1. Getting initial data. Autorizado para usar la cuenta %1. Recibiendo datos iniciales. There is no authorized account. No hay ninguna cuenta autorizada. Getting list of 'Following'... Recibiendo lista de 'Siguiendo'... Getting list of 'Followers'... Recibiendo lista de 'Seguidores'... Getting site users for %1... %1 is a server name Recibiendo usuarios del servidor %1... Getting list of person lists... Recibiendo lista de listas de personas... Creating person list... Creando lista de personas... Deleting person list... Borrando lista de personas... Getting a person list... Recibiendo una lista de personas... Adding person to list... Añadiendo una persona a una lista... Removing person from list... Quitando a una persona de una lista... Creating group... Creando grupo... Joining group... Uniéndose al grupo... Leaving group... Saliendo del grupo... Main timeline update requested, but updates are blocked. Actualización de línea temporal principal solicitada, pero las actualizaciones están bloqueadas. Getting main timeline... Recibiendo línea temporal principal... Direct timeline update requested, but updates are blocked. Actualización de línea temporal directa solicitada, pero las actualizaciones están bloqueadas. Getting direct messages timeline... Recibiendo línea temporal de mensajes directos... Activity timeline update requested, but updates are blocked. Actualización de línea temporal de actividad solicitada, pero las actualizaciones están bloqueadas. Getting activity timeline... Recibiendo línea temporal de actividad... Favorites timeline update requested, but updates are blocked. Actualización de línea temporal de favoritos solicitada, pero las actualizaciones están bloqueadas. Getting favorites timeline... Recibiendo línea temporal de favoritos... Timeline update requested, but updates are blocked. Actualización de línea temporal solicitada, pero las actualizaciones están bloqueadas. Getting timeline... Recibiendo línea temporal... Timeline Línea temporal Messages Mensajes User timeline Línea temporal de usuario Uploading %1 1=filename Subiendo %1 HTTP error For the following HTTP error codesyou can check http://en.wikipedia.org/wiki/List_of_HTTP_status_codes in your language Error HTTP Gateway Timeout HTTP 504 error string Tiempo de espera de la pasarela agotado Service Unavailable HTTP 503 error string Servicio no disponible Not Implemented HTTP 501 error string No implementado Internal Server Error HTTP 500 error string Error interno Gone HTTP 410 error string Ya no disponible Not Found HTTP 404 error string No encontrado Forbidden HTTP 403 error string Prohibido Unauthorized HTTP 401 error string No autorizado Bad Request HTTP 400 error string Solicitud incorrecta Moved Temporarily HTTP 302 error string Movido temporalmente Moved Permanently HTTP 301 error string Movido permanentemente Profile received. Perfil recibido. Followers Seguidores Following Siguiendo Profile updated. Perfil actualizado. E-mail updated: %1 E-mail actualizado: %1 %1 published successfully. Updating post content... %1 is the type of object: note, image... Se ha publicado '%1' correctamente. Actualizando contenido del mensaje... Untitled post %1 published successfully. %1 is a piece of the post Mensaje sin título %1 publicado correctamente. Post %1 published successfully. %1 is the title of the post Mensaje %1 publicado correctamente. Avatar published successfully. Avatar publicado correctamente. Untitled post %1 updated successfully. %1 is a piece of the post Mensaje sin título %1 actualizado correctamente. Post %1 updated successfully. %1 is the title of the post Mensaje %1 actualizado correctamente. Comment %1 updated successfully. %1 is a piece of the comment Comentario %1 actualizado correctamente. Comment %1 posted successfully. %1 is a piece of the comment Comentario %1 publicado correctamente. %1 attempts %1 intentos 1 attempt 1 intento Some initial data was not received. Restarting initialization... Algunos datos iniciales no se han recibido. Reiniciando la inicialización... Post updated successfully. Mensaje actualizado correctamente. Comment updated successfully. Comentario actualizado correctamente. Message liked or unliked successfully. Mensaje marcado o desmarcado "Me gusta" correctamente. Likes received. meh... "Me gusta" recibidos. Comment '%1' posted successfully. %1 is a piece of the comment Comentario '%1' publicado correctamente. 1 comment received. 1 comentario recibido. %1 comments received. %1 comentarios recibidos. Post by %1 shared successfully. 1=author of the post we are sharing Mensaje de %1 compartido correctamente. Received '%1'. %1 is the name of a feed Se ha recibido '%1'. Adding items... Añadiendo elementos... SSL errors in connection to %1! ¡Errores de SSL en la conexión a %1! Loading external image from %1 regardless of SSL errors, as configured... %1 is a hostname Cargando imagen externa de %1 a pesar de los errores SSL, como se ha configurado... OAuth error while authorizing application. Error de OAuth mientras se autorizaba a la aplicación. Message deleted successfully. Mensaje eliminado correctamente. The comments for this post cannot be loaded due to missing data on the server. Los comentarios de este mensaje no se pueden cargar debido a que faltan datos en el servidor. Getting '%1'... %1 is the name of a feed Recibiendo '%1'... Main timeline Línea temporal principal Direct messages Mensajes directos Activity Actividad Favorites Favoritos Meanwhile Mientras tanto Mentions Menciones Actions Acciones Getting '%1'... %1 is a feed's name Recibiendo "%1"... List of 'lists' received. Lista de 'listas' recibida. Person list deleted successfully. Lista de personas borrada correctamente. %1 (%2) added to list successfully. 1=contact name, 2=contact ID Se ha añadido a %1 (%2) a la lista correctamente. %1 (%2) removed from list successfully. 1=contact name, 2=contact ID Se ha eliminado a %1 (%2) de la lista correctamente. Group %1 created successfully. Grupo %1 creado correctamente. Group %1 joined successfully. Se ha entrado al grupo %1 correctamente. Left the %1 group successfully. Se ha salido del grupo %1 correctamente. File downloaded successfully. Archivo descargado correctamente. Avatar uploaded. Avatar subido. Loading embedded image from %1 regardless of SSL errors, as configured %1 is a hostname Cargado imagen incrustada desde %1 a pesar de los errores SSL, como se ha configurado The application is not registered with your server yet. Registering... La aplicación no está registrada con tu servidor todavía. Registrando... Getting OAuth token... Recibiendo identificador de autorización (token) de OAuth... OAuth support error Error de soporte de OAuth Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support. Tu instalación de QOAuth, una biblioteca usada por Dianara, no parece tener soporte de HMAC-SHA1. You probably need to install the OpenSSL plugin for QCA: %1, %2 or similar. Probablemente necesites instalar el conector de OpenSSL para QCA: %1, %2 o similar. Authorization error Error de autorización There was an OAuth error while trying to get the authorization token. Ha habido un error de OAuth mientras se intentaba obtener un identificador de autorización (token). QOAuth error %1 Error de QOAuth %1 Application authorized successfully. Aplicación autorizada correctamente. Waiting for proxy password... Esperando contraseña del proxy... Still waiting for profile. Trying again... Aún se está esperando el perfil. Intentándolo de nuevo... Some initial data was not received. Restarting initialization. Algunos datos iniciales no se han recibido. Reiniciando la inicialización. Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally. Algunos datos iniciales no se han recibido tras varios intentos. Algo puede estar fallando en tu servidor. Es posible que puedas usar el servicio con normalidad. All initial data received. Initialization complete. Se han recibido todos los datos iniciales. Inicialización completada. Ready. Preparado. SiteUsersList You can get a list of the newest users registered on your server by clicking the button below. Puedes recibir una lista de los usuarios más nuevos registrados en tu servidor, haciendo clic en el botón de abajo. More resources to find users: Más recursos para encontrar usuarios: Wiki page 'Users by language' Página wiki 'Usuarios por idioma' PPump user search service at inventati.org Servicio PPump de búsqueda de usuarios en inventati.org Get list of users from your server Recibir lista de usuarios de tu servidor Close list Cerrar la lista Loading... Cargando... %1 users in %2 %1 = user count, %2 = server name %1 usuarios en %2 TimeLine Welcome to Dianara Bienvenido a Dianara Dianara is a <b>pump.io</b> client. Dianara es un cliente <b>pump.io</b>. Dianara is a <b>Pump.io</b> client. Dianara es un cliente <b>Pump.io</b>. Press <b>F1</b> if you want to open the Help window. Pulsa <b>F1</b> si quieres abrir la ventana de ayuda. First, configure your account from the <b>Settings - Account</b> menu. En primer lugar, configura tu cuenta desde el menú <b>Configuración - Cuenta</b>. After the process is done, your profile and timelines should update automatically. Cuando el proceso esté listo, tu perfil y líneas temporales deberían de actualizarse automáticamente. Take a moment to look around the menus and the Configuration window. Tómate un momento para echar un vistazo por los menús y la ventana de Configuración. You can also set your profile data and picture from the <b>Settings - Edit Profile</b> menu. También puedes rellenar tu información de perfil y foto desde el menú <b>Configuración - Editar perfil</b>. Dianara's blog Blog de Dianara Newest Lo más nuevo Newer Más nuevos Older Más antiguos Requesting... Solicitando... Loading... Cargando... Page %1 of %2. Página %1 de %2. Showing %1 posts per page. Mostrando %1 mensajes por página. %1 posts in total. %1 mensajes en total. Click here or press Control+G to jump to a specific page Haz clic aquí o pulsa Control+G para saltar a una página específica '%1' cannot be updated because a comment is currently being composed. %1 = feed's name No se puede actualizar '%1' porque se está editando un comentario. %1 more posts pending for next update. %1 mensajes más pendientes para la próxima actualización. Click here to receive them now. Haz clic aquí para recibirlos ahora. There are no posts No hay mensajes Get %1 pending posts Recibir %1 mensajes pendientes If you don't have a Pump account yet, you can get one at the following address, for instance: Si aún no tienes una cuenta Pump, puedes conseguir una en la siguiente dirección, por ejemplo: There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information. Hay descripciones emergentes (tooltips) por todas partes, por lo que si mantienes el ratón sobre un botón o un campo de texto, es probable que veas alguna información adicional. Pump.io User Guide Guía de usuario de Pump.io Direct Messages Timeline Línea temporal de mensajes directos Here, you'll see posts specifically directed to you. Aquí verás los mensajes dirigidos específicamente a ti. Activity Timeline Línea temporal de actividad You'll see your own posts here. Aquí verás tus propios mensajes. Favorites Timeline Línea temporal de favoritos Posts and comments you've liked. Mensajes y comentarios que te han gustado. Timestamp Invalid timestamp! ¡Hora/fecha no válida! A minute ago Hace un minuto %1 minutes ago Hace %1 minutos An hour ago Hace una hora %1 hours ago Hace %1 horas Just now Ahora mismo In the future En el futuro Yesterday Ayer %1 days ago Hace %1 días A month ago Hace un mes %1 months ago Hace %1 meses A year ago Hace un año %1 years ago Hace %1 años UserPosts Posts by %1 Mensajes de %1 Loading... Cargando... &Close &Cerrar Received '%1'. Se ha recibido '%1'. %1 posts %1 mensajes Error loading the timeline Error cargando la línea temporal dianara-v1.3.2/translations/dianara_ca.ts000644 000764 000764 00000647740 12614520443 020052 0ustar00janjan000000 000000 ASActivity Public Públic %1 by %2 1=kind of object: note, comment, etc; 2=author's name %1 de %2 ASObject Note Noun, an object type Nota Article Noun, an object type Article Image Noun, an object type Imatge Audio Noun, an object type Àudio Video Noun, an object type Vídeo File Noun, an object type Arxiu Comment Noun, as in object type: a comment Comentari Group Noun, an object type Grup Collection Noun, an object type Col·lecció Other As in: other type of post Altre No detailed location No hi ha ubicació detallada Deleted on %1 Eliminat el %1 and one other i un més and %1 others i %1 més ASPerson Hometown Ciutat AccountDialog Your Pump.io address: La teva adreça pump.io: Webfinger ID, like username@pumpserver.org ID Webfinger, tipus usuari@servidorpump.org Your address, as username@server La teva adreça, com usuari@servidor Get &Verifier Code Obtenir codi de &verificació Verifier code: Codi de verificació: Enter or paste the verifier code provided by your Pump server here Introdueix o enganxa aquí el codi de verificació proporcionat pel teu servidor Pump &Save Details &Guardar dades If the browser doesn't open automatically, copy this address manually Si el navegador web no s'obre automàticament, copia aquesta adreça manualment Account Configuration Configuració del compte First, enter your Webfinger ID, your pump.io address. Primer, introdueix el teu identificador Webfinger, la teva adreça pump.io. Your address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. La teva adreça es com usuari@servidorpump.org, i pots trobar-la al teu perfil, a la interfície web. If your profile is at https://pump.example/yourname, then your address is yourname@pump.example Si el teu perfil es troba a https://pump.exemple/elteunom, llavors la teva adreça es elteunom@pump.exemple If you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. 1=link to website Si encara no tens un compte, pots registrar-ne un a %1. Aquest enllaç et portarà a un servidor públic aleatori. If you need help: %1 Si necessites ajuda: %1 Pump.io User Guide Guia d'usuari de Pump.io Your address, like username@pumpserver.org La teva adreça, com usuari@servidorpump.org After clicking this button, a web browser will open, requesting authorization for Dianara Quan premis aquest botó, s'obrirà un navegador web, demanant autorització per Dianara Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. Don't translate the VERIFIER word! Un cop que hagis autoritzat a Dianara des de la interfície web del teu servidor Pump, rebràs un codi anomenat VERIFIER. Copia'l i enganxa'l al camp de sota. Enter the verifier code provided by your Pump server here Introdueix aquí el codi de verificació proporcionat pel teu servidor Pump Paste the verifier here Enganxa el verificador aquí &Authorize Application &Autoritzar l'aplicació &Cancel &Cancel·lar Your account is properly configured. El teu compte està configurat correctament. Press Unlock if you wish to configure a different account. Prem Desbloquejar si vols configurar un compte diferent. &Unlock &Desbloquejar A web browser will start now, where you can get the verifier code Ara s'iniciarà un navegador web, on podràs obtenir el codi de verificació Your Pump address is invalid La teva adreça Pump no es vàlida Verifier code is empty El codi de verificació està buit Dianara is authorized to access your data Dianara te autorització per accedir al teu compte AudienceSelector 'To' List Llista 'Per a' 'CC' List Llista 'CC' 'Cc' List Llista 'Cc' &Add to Selected &Afegir a seleccionats All Contacts Tots els contactes Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. Selecciona gent de la llista de l'esquerra. Pots arrossegar-los amb el ratolí, fer clic o doble clic en ells, o seleccionar-los i fer servir el botó de sota. Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. ON THE LEFT should change to ON THE RIGHT in RTL languages Selecciona gent de la llista de l'esquerra. Pots arrossegar-los amb el ratolí, fer clic o doble clic en ells, o seleccionar-los i fer servir el botó de sota. Clear &List Esborrar &llista &Done &Fet &Cancel &Cancel·lar Selected People Gent seleccionada AvatarButton Open %1's profile in web browser Obrir el perfil de %1 al navegador web Open your profile in web browser Obrir el teu perfil al navegador web Send message to %1 Enviar missatge a %1 Browse messages Veure missatges Stop following Deixar de seguir Follow Seguir Stop following? Deixar de seguir? Are you sure you want to stop following %1? Estàs segur de que vols deixar de seguir a %1? &Yes, stop following &Sí, deixar de seguir &No &No ColorPicker Change Canviar Choose a color Escull un color Comment Posted on %1 Publicat el %1 Modified on %1 Modificat el %1 Like or unlike this comment Dir que t'agrada o que ja no t'agrada aquest comentari Quote This is a verb, infinitive Citar Reply quoting this comment Respondre citant aquest comentari Edit Editar Modify this comment Modificar aquest comentari Delete Eliminar Erase this comment Esborrar aquest comentari Unlike Ja no m'agrada Like M'agrada %1 like this comment Plural: %1=list of people like John, Jane, Smith A %1 els hi agrada aquest comentari %1 likes this comment Singular: %1=name of just 1 person A %1 li agrada aquest comentari WARNING: Delete comment? AVÍS: Eliminar comentari? Are you sure you want to delete this comment? Estàs segur de que vols eliminar aquest comentari? &Yes, delete it &Sí, eliminar-ho &No &No CommenterBlock You can press Control+Enter to send the comment with the keyboard Pots premer Control+Enter per enviar el comentari amb el teclat &Cancel &Cancel·lar Reload comments Actualitzar comentaris Comment Infinitive verb Comentar Cancel Cancel·lar Press ESC to cancel the comment if there is no text Prem ESC per cancel·lar el comentari si no hi ha text Check for comments Comprovar comentaris Show all %1 comments Mostrar els %1 comentaris Comments are not available Els comentaris no es troben disponibles Error: Already composing Error: Ja s'està redactant You can't edit a comment at this time, because another comment is already being composed. No pots editar un comentari en aquest moment, perque ja s'està redactant un altre comentari. Editing comment Editant comentari Posting comment failed. Try again. Ha fallat la publicació del comentari. Torna-ho a provar. Sending comment... Enviant comentari... Updating comment... Actualitzant comentari... Comment is empty. El comentari està buit. Composer Bold Negreta Italic Cursiva Make a link Fer un enllaç Click here or press Control+N to post a note... Fes clic aquí o prem Control+N per publicar una nota... Type a message here to post it Escriu un missatge aquí per publicar-ho Normal Normal Symbols Símbols Formatting Format Underline Subratllat Strikethrough Barrat Header Títol List Llista Table Taula Preformatted block Bloc preformatat Quote block Bloc de cita Insert an image from a web site Inserir una imatge des d'un lloc web Insert line Inserir línia Insert as image? Inserir com imatge? The link you are pasting seems to point to an image. Sembla que l'enllaç que estàs enganxant apunta a una imatge. Insert as visible image Inserir com imatge visible Insert as link Inserir com enllaç Table Size Mida de la taula How many rows (height)? Quantes files (alçada)? How many columns (width)? Quantes columnes (amplada)? Type or paste a web address here. You could also select some text first, to turn it into a link. Escriu o enganxa una adreça web aquí. També pots seleccionar text abans, per convertir-ho en un enllaç. Type or paste the image address here. The link must point to the image file directly. Escriu o enganxa l'adreça de la imatge aquí. L'enllaç ha d'apuntar a l'arxiu d'imatge directament. Text Formatting Options Opcions de format de text Paste Text Without Formatting Enganxar text sense format Insert an image from a URL Inserir una imatge des d'una URL Error: Invalid URL Error: URL no vàlida The address you entered (%1) is not valid. Image addresses should begin with http:// or https:// L'adreça que has introduit (%1) no es vàlida. Les adreces d'imatges han de començar amb http:// o https:// Cancel message? Cancel·lar el missatge? Are you sure you want to cancel this message? Segur que vols cancel·lar aquest missatge? &Yes, cancel it &Sí, cancel·lar-ho &No &No Insert a link Inserir un enllaç &Format Button for text formatting and related options &Format Type a comment here Escriu un comentari aquí Make a link from selected text Fer un link del text seleccionat Type or paste a web address here. The selected text (%1) will be converted to a link. Escriu o enganxa una adreça web aquí. El text seleccionat (%1) serà convertit en un enllaç. ConfigDialog minutes minuts Top Part superior Bottom Part inferior Left side Costat esquerre Right side Costat dret Program Configuration Configuració del programa Timeline &update interval Interval d'&actualització de la línia temporal &Tabs position Posició de les &pestanyes &Movable tabs Pes&tanyes mòbils Minor Feeds Línies temporals menors posts Goes after a number, as: 25 posts missatges &Posts per page, main timeline &Missatges per pàgina, línia temporal principal posts This goes after a number, like: 10 posts missatges Posts per page, &other timelines Missatges per pàgina, &altres línies temporals Public posts as &default Missatges públics de manera &predeterminada Pro&xy Settings Paràmetres de pro&xy Network configuration Configuració de xarxa Set Up F&ilters Configurar f&iltres Filtering rules Normes de filtrat Highlighted activities, except mine Activitats destacades, excepte les meves Any highlighted activity Qualsevol activitat destacada Always Sempre Never Mai Show snippets in minor feed Mostrar fragments a la ĺinia temporal menor characters This is a suffix, after a number caràcters Snippet limit Límit dels fragments Post Titles Títols del missatges Post Contents Contingut dels missatges Comments Comentaris Minor Feed Línia temporal menor You are among the recipients of the activity, such as a comment addressed to you. Et trobes entre els destinataris de l'activitat, com per exemple un comentari dirigit a tu. Used also when highlighting posts addressed to you in the timelines. Utilitzat també quan es destaquen missatges dirigits a tu a les línies temporals. The activity is in reply to something done by you, such as a comment posted in reply to one of your notes. La activitat es en resposta a alguna cosa feta per tu, com un comentari publicat en resposta a una de les teves notes. You are the object of the activity, such as someone adding you to a list. Ets l'objecte de l'activitat, com per exemple, quan algú t'afegeix a una llista. The activity is related to one of your objects, such as someone liking one of your posts. L'activitat està relacionada amb un dels teus objectes, com per exemple quan a algú li agrada un dels teus missatges. Used also when highlighting your own posts in the timelines. Utilitzat també quan es destaquen els teus propis missatges a les línies temporals. Item highlighted due to filtering rules. Element destacat per normes de filtrat. Item is new. L'element es nou. Show snippets in minor feeds Mostrar fragments a les ĺinies temporals menors Hide duplicated posts Ocultar missatges duplicats Only for embedded images Només per imatges incrustades... Avatar size Mida dels avatars Show extended share information Mostrar informació adicional als compartits Show extra information Mostrar informació extra Highlight post author's comments Destacar comentaris de l'autor del missatge Highlight your own comments Destacar els teus propis comentaris Ignore SSL errors in images Ignorar errors d'SSL a les imatges Show character counter Mostrar contador de caràcters Don't inform followers when following someone No informar als seguidors al seguir a algú Don't inform followers when handling lists No informar als seguidors al manipular llistes Only inform the author when liking posts and comments Informar només a l'autor dels "M'agrada" en missatges i comentaris As system notifications Com a notificacions del sistema Using own notifications Fent servir notificacions pròpies Don't show notifications No mostrar notificacions Notification Style Estil de les notificacions Notify when receiving: Notificar quan es rebin: New posts Missatges nous Highlighted posts Missatges destacats New activities in minor feed Noves activitats a la línia temporal menor Highlighted activities in minor feed Activitats destacades a la línia temporal menor Default icona, femeni Predeterminada System iconset, if available Icona del sistema, si es troba disponible Show your current avatar Mostrar el teu avatar actual Custom icon Icona personalitzada System Tray Icon &Type &Tipus d'icona a la safata del sistema Hide window on startup Ocultar finestra a l'inici General Options Opcions generals Fonts Tipus de lletra Colors Colors Timelines Línies temporals Posts Missatges Composer Editor Privacy Privacitat Notifications Notificacions System Tray Safata del sistema S&elect... S&eleccionar... Custom &Icon &Icona personalitzada Select custom icon Selecciona icona personalitzada Dianara stores data in this folder: Dianara emmagatzema dades en aquesta carpeta: Left side tabs on left side/west; RTL not affected Costat esquerre Right side tabs on right side/east; RTL not affected Costat dret Show information for deleted posts Mostrar informació dels missatges eliminats Jump to new posts line on update Saltar a la línia de nous missatges quan s'actualitzi Only for images inserted from web sites. Només per imatges inserides des de llocs web. Use with care. Utilitzar amb cura. Use attachment filename as initial post title Utilitzar nom de l'adjunt com a títol inicial del missatge Inform only the author when liking things Informar només a l'autor dels "M'agrada" &Save Configuration &Guardar configuració &Cancel &Cancel·lar This is a system notification Això es una notificació del sistema System notifications are not available! Les notificacions del sistema no estan disponibles! Own notifications will be used. Es faran servir les notificacions pròpies. This is a basic notification Això és una notificació bàsica Image files Arxius d'imatge All files Tots els arxius Invalid image Imatge no vàlida The selected image is not valid. La imatge seleccionada no es vàlida. ContactCard Hometown Ciutat Joined: %1 Es va unir: %1 Updated: %1 Actualitzat: %1 Bio for %1 Abbreviation for Biography, but you can use the full word; %1=contact name Bio de %1 This user doesn't have a biography Aquest usuari no té una biografia No biography for %1 %1=contact name No hi ha biografia per %1 Open Profile in Web Browser Obrir el perfil al navegador web Send Message Enviar missatge Browse Messages Veure missatges In Lists... En llistes... User Options Opcions d'usuari Follow Seguir Stop Following Deixar de seguir Stop following? Deixar de seguir? Are you sure you want to stop following %1? Estàs segur de que vols deixar de seguir a %1? &Yes, stop following &Sí, deixar de seguir &No &No ContactList Type a partial name or ID to find a contact... Escriu una part d'un nom o ID per trobar un contacte... F&ull List Llista c&ompleta ContactManager username@server.org or https://server.org/username usuari@servidor.org o https://servidor.org/usuari &Enter address to follow: &Introdueix adreça per seguir: &Follow &Seguir Reload Followers Actualitzar Seguidors Reload Following Actualitzar Seguint Export Followers Exportar Seguidors Export Following Exportar Seguint Reload Lists Actualitzar llistes &Neighbors V&eïns Site &Users "del lloc"? &Usuaris locals Follo&wers Se&guidors Followin&g Segui&nt &Lists &Llistes Export list of 'following' to a file Exportar llista de 'seguint' a un arxiu Export list of 'followers' to a file Exportar llista de 'seguidors' a un arxiu DownloadWidget Download Descarregar Save the attached file to your folders Guardar l'arxiu adjunt a les teves carpetes Cancel Cancel·lar Save File As... Guardar arxiu com... All files Tots els arxius Abort download? Interrompre la descàrrega? Do you want to stop downloading the attached file? Vols aturar la descàrrega de l'arxiu adjunt? &Yes, stop &Sí, aturar &No, continue &No, continuar Download aborted Descàrrega interrompuda Download completed Descàrrega completada Download failed Ha fallat la descàrrega Downloading %1 KiB... Descarregant %1 KiB... %1 KiB downloaded hmmmmmmmm FIXME %1 KiB descarregats EmailChanger Change E-mail Address Canviar adreça d'e-mail Change Canviar &Cancel &Cancel·lar E-mail Address: Adreça d'e-mail: Again: Una altra vegada: Repeat: Repeteix: Your Password: La teva contrasenya: E-mail addresses don't match! Les adreces d'e-mail no coincideixen! Password is empty! La contrasenya està buida! FilterEditor Filter Editor Editor de filtres Application Aplicació %1 if %2 contains: %3 This explains a filter rule, like: Hide if Author ID contains JohnDoe %1 si %2 conté: %3 Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. Aquí pots establir algunes normes per ocultar o destacar coses. Pots filtrar per contingut, autor o aplicació. Per exemple, pots filtrar missatges publicats per la aplicació Open Farm Game, o que contenen la paraula NSFW al missatge. També podries destacar missatges que contenen el teu nom. Hide Amagar Highlight Destacar Post Contents Contingut de la publicació Author ID ID de l'autor Activity Description Descripció de l'activitat Keywords... Paraules clau... &Add Filter &Afegir filtre Filters in use Filtres en ús &Remove Selected Filter &Eliminar filtre seleccionat &Save Filters &Guardar filtres &Cancel &Cancel·lar if si contains conté &New Filter &Nou filtre C&urrent Filters Filtres &actuals FirstRunWizard First Run Wizard Auxiliar de primera execució Welcome Wizard Auxiliar de benvinguda Welcome to Dianara! Benvingut a Dianara! This wizard will help you get started. Aquest auxiliar t'ajudarà a començar. You can access this window again at any time from the Help menu. Pots tornar a accedir a aquesta finestra en qualsevol moment des del menú d'Ajuda. The first step is setting up your account, by using the following button: El primer pas es configurar el teu compte, utilitzant el següent botó: Configure your &account Configur&a el teu compte Once you have configured your account, it's recommended that you edit your profile and add an avatar and some other information, if you haven't done so already. Un cop que hagis configurat el teu compte, es recomanable que editis el teu perfil i afegeixis un avatar i alguna informació més, si no ho has fet encara. &Edit your profile &Edita el teu perfil By default, Dianara will post only to your followers, but it's recommended that you post to Public, at least sometimes. De manera predeterminada, Dianara publicarà només pels teus seguidors, però es recomanable que publiquis per a Públic, com a mínim de tant en tant. Post to &Public by default Publicar per a &Públic de manera predeterminada Open general program &help window Obrir la finestra d'aj&uda general del programa &Show this again next time Dianara starts Tornar a &mostrar la propera vegada que Dianara arrenqui &Close &Tancar FontPicker Change Canviar Choose a font Escull un tipus de lletra HelpWidget Basic Help Ajuda bàsica Getting started Començant The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. La primera vegada que arrenquis Dianara, hauries de veure la finestra de Configuració del compte. Allà, introdueix la teva adreça de Pump.io com nom@servidor i prem el botó Obtenir codi de verificació. Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. A continuació, el teu navegador web habitual hauria de carregar la pàgina d'autorització al teu servidor Pump.io. Allà, hauràs de copiar el codi 'VERIFIER' complet, i enganxar-ho al segon camp de Dianara. Llavors prem 'Autoritzar l'aplicació', i quan sigui confirmat, prem 'Guardar dades'. At this point, your profile, contact lists and timelines will be loaded. En aquest moment,es carregarà el teu perfil, les llistes de contactes i les línies temporals. You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. Hauries de fer una ullada a la finestra 'Configuració del programa', al menú Configuració - Configurar Dianara. Allà trobaràs diverses opcions interessants. Settings Configuració You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. Pots ajustar diverses coses al teu gust a la configuració, com l'interval de temps entre actualitzacions de la línia temporal, quants missatges per pàgina vols, colors per destacar missatges, notificacions o quin aspecte tindrà la icona de la safata del sistema. Timelines Línies temporals Contents Taula de continguts Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. Recorda que hi ha molts llocs a Dianara on pots obtenir més informació mantenint el ratolí sobre algun text o botó, i esperant a que aparegui l'indicador de funció (tooltip). Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. Aquí, també pots activar l'opció per publicar sempre els teus missatges com públics de forma predeterminada. Sempre pots canviar això en el moment de publicar. The main timeline, where you'll see all the stuff posted or shared by the people you follow. La línia temporal principal, on veuràs tot el que ha publicat o compartit la gent a la que segueixes. Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. Línia temporal de missatges, on veuràs missatges enviats a tu específicament. Aquests missatges poden haver estat enviats també a altres persones. Activity timeline, where you'll see your own posts, or posts shared by you. Línia temporal d'activitat, on veuràs els teus propis missatges, o missatges que has compartit. Favorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. Línia temporal de favorits, on veuràs els missatges que t'han agradat. Això es pot fer servir com un sistema d'adreces d'interès. The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. La cinquena línia temporal es la línia temporal menor, també coneguda com el "Mentrestant". És visible a la banda esquerra, encara que es pot amagar. Aquí veuràs activitats secundàries fetes per tothom, com ara accions de comentar, marcar missatges amb "M'agrada" o seguir a gent. These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. Aquestes activitats poden tenir un botó "+". Prem-ho per obrir el missatge al que fan referència. A més, com a molts altres llocs, pots mantenir el ratolí a sobre per veure informació rellevant a l'indicador de funció (tooltip). Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. Dins de la pestanya 'Veïns' veuràs alguns recursos per trobar gent, i tindràs la opció de veure els últims usuaris registrats al teu servidor, directament. Dianara offers a D-Bus interface that allows some control from other applications. Dianara ofereix una interfície D-Bus que permet un cert control des d'altres aplicacions. The interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. La interfície es troba a %1, i pots accedir-hi amb eines com %2 o %3. Ofereix mètodes com %4 i %5. Posting Publicant If you're new to Pump.io, take a look at this guide: Si es la primera vegada que utilitzes Pump.io, fes una ullada a aquesta guia: New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. Els missatges nous es mostren destacats en un color diferent. Els pots marcar com llegits fent clic a qualsevol part buida del missatge. You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. Pots publicar notes fent clic al camp de text de la part superior de la finestra o prement Control+N. Afegir un títol al missatge és opcional, però molt recomanable, ja que ajudarà a identificar millor les referències al teu missatge a la línia temporal menor, notificacions per e-mail, etc. It is possible to attach images, audio, video, and general files, like PDF documents, to your post. És possible adjuntar imatges, àudio, vídeo i arxius generals, com documents PDF, al teu missatge. You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. Pots fer servir el botó Format per afegir format al text, com ara negreta o cursiva. Algunes d'aquestes opcions necessiten que el text sigui seleccionat abans d'utilitzar-les. You can select who will see your post by using the To and CC buttons. Pots seleccionar qui veurà el teu missatge fent servir els botons 'Per a' i 'CC'. If you add a specific person to the 'To' list, they will receive your message in their direct messages tab. Si afegeixes una persona específica a la llista 'Per a', rebrà el teu missatge a la seva pestanya de missatges directes. You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. Pots fer missatges privats afegint gent específica a aquestes llistes, i desmarcant les opcions Públic i Seguidors. Managing contacts Gestionant els contactes You can see the lists of people you follow, and who follow you from the Contacts tab. Pots veure les llistes de gent que segueixes, i que et segueix, a la pestanya Contactes. There, you can also manage person lists, used mainly to send posts to specific groups of people. Allà pots gestionar també les llistes de persones, que s'utilitzen principalment per enviar missatges a grups de gent específics. You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. Pots fer clic a qualsevol avatar als missatges, als comentaris, i a la columna "Mentrestant", i veuràs un menú amb algunes opcions, una de les quals és seguir o deixar de seguir a aquesta persona. Keyboard controls Control per teclat Pump.io User Guide Guia d'usuari de Pump.io There are seven timelines: Hi ha set línies temporals: The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). La sisena i la setena línies temporals també son línies temporals menors, semblants al "Mentrestant", pero contenen només activitats dirigides a tu (Mencions) i activitats fetes per tu (Accions). You can select who will see your post by using the To and Cc buttons. Pots seleccionar qui veurà el teu missatge fent servir els botons 'Per a' i 'Cc'. You can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. També pots teclejar '@' i els primers caràcters del nom d'un contacte per mostrar un menú emergent amb opcions que coincideixin. Choose one with the arrows and press Enter to complete the name. This will add that person to the recipients list. Escull una amb les tecles de cursor i prem Enter per completar el nom. Això afegirà aquesta persona a la llista de destinataris. You can also send a direct message (initially private) to that contact from this menu. També pots enviar un missatge directe (inicialment privat) a aquest contacte des d'aquest menú. You can find a list with some Pump.io users and other information here: Pots trobar una llista amb alguns usuaris de Pump.io i altres dades aquí: Users by language Usuaris per idioma The most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. Les accions més comunes que es troben als menús tenen tecles de drecera escrites al costat, com F5 o Control+N. Besides that, you can use: A part d'això, pots utilitzar: Control+Up/Down/PgUp/PgDown/Home/End to move around the timeline. Hmm.... Control+Amunt/Avall/RePag/AvPag/Inici/Fi per moure't per la línia temporal. Control+Left/Right to jump one page in the timeline. Control+Esquerra/Dreta per passar una pàgina a la línia temporal. Control+G to go to any page in the timeline directly. Control+G per anar a qualsevol pàgina de la línia temporal directament. Control+1/2/3 to switch between the minor feeds. Control+1/2/3 per canviar entre les línies temporals menors. Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. Control+Enter per publicar, quan hagis acabat de redactar una nota o un comentari. Si la nota es troba buida, la pots cancel·lar prement ESC. Command line options Opcions de línia d'ordres The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. LEFT SIDE should change to RIGHT SIDE on RTL languages La cinquena línia temporal es la línia temporal menor, també coneguda com el "Mentrestant". És visible a la banda esquerra, encara que es pot amagar. Aquí veuràs activitats secundàries fetes per tothom, com ara accions de comentar, marcar missatges amb "M'agrada" o seguir a gent. Choose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. Escull una amb les tecles de cursor i prem Enter per completar el nom. Això afegirà aquesta persona a la llista de destinataris. There is a text field at the top, where you can directly enter addresses of new contacts to follow them. Hi ha un camp de text a la part superior, on pots introduir directament adreces de contactes nous per seguir-los. While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. Mentre redactes una nota, prem Enter per passar del títol al cos del missatge. A més, prement la fletxa amunt quan siguis al principi del missatge, torna al títol. Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. Control+Enter per acabar de crear una llista de destinataris per un missatge, a les llistes 'Per a' o 'Cc'. You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. Pots fer servir el paràmetre --config per executar el programa amb una configuració diferent. Això pot ser útil per utilitzar dos o més comptes diferents. Fins i tot pots executar dues instàncies de Dianara a la vegada. Use the --debug parameter to have extra information in your terminal window, about what the program is doing. Fes servir el paràmetre --debug per tenir informació addicional a la finestra de la terminal, sobre el que està fent el programa. If your server does not support HTTPS, you can use the --nohttps parameter. Si el teu servidor no té suport HTTPS, pots utilitzar el paràmetre --nohttps. If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. Si fas servir una configuració alternativa, amb alguna cosa com '--config altreconf', llavors la interfície estarà a org.nongnu.dianara_altreconf. &Close &Tancar ImageViewer Image Imatge Resolution and file size Resolució i mida de l'arxiu ESC to close, secondary-click for options ESC per tancar, clic secundari per opcions &Save As... &Guardar com... &Restart Animation &Reiniciar animació &Close &Tancar Save Image... Guardar imatge... Close Viewer Tancar visualitzador Save Image As... Guardar imatge com... Image files Arxius d'imatge All files Tots els arxius Error saving image Error guardant la imatge There was a problem while saving %1. Filename should end in .jpg or .png extensions. Hi ha hagut un problema al guardar %1. El nom de l'arxiu hauria d'acabar amb l'extensió .jpg o .png. ListsManager Name Nom Members Membres Add Mem&ber Afegir mem&bre &Remove Member &Eliminar membre &Delete Selected List &Esborrar llista seleccionada Add New &List Afegir nova &llista Create L&ist Crear ll&ista &Add to List &Afegir a llista Are you sure you want to delete %1? 1=Name of a person list Estàs segur de que vols eliminar %1? Remove person from list? Treure persona de la llista? Are you sure you want to remove %1 from the %2 list? 1=Name of a person, 2=name of a list Estàs segur de que vols treure a %1 de la llista %2? &Yes &Si Type a name for the new list... Escriu un nom per la nova llista... Type an optional description here Escriu una descripció opcional aquí WARNING: Delete list? AVÍS: Eliminar llista? &Yes, delete it &Sí, eliminar-la &No &No LogViewer Log Registre Clear &Log Esborrar el &registre &Close &Tancar MainWindow &Messages &Missatges &Contacts &Contactes &Quit &Sortir &Session &Sessió Meanwhile... Mentrestant... Side &Panel Cuadre &lateral Status &Bar &Barra d'estat &Timeline Línia &temporal &Activity &Activitat Initializing... Inicialitzant... Your account is not configured yet. El teu compte no està configurat encara. Dianara started. Dianara iniciat. Minor activities done by everyone, such as replying to posts Activitats menors fetes per tothom, com per exemple respostes a missatges Minor activities addressed to you Activitats menors dirigides a tu Actions Accions Minor activities done by you Activitats menors fetes per tu The people you follow, the ones who follow you, and your person lists La gent a la que segueixes, la que et segueix, i les teves llistes de persones Running with Qt v%1. Funcionant amb Qt v%1. &Update Timeline Act&ualitzar línia temporal Update &Messages Actualitzar &missatges Update &Activity Actualitzar &activitat Update Fa&vorites Actualitzar &favorits Update Mentions Actualitzar mencions Update Actions Actualitzar accions Auto-update &Timelines Auto-actualitzar línies &temporals Mark All as Read Marcar tot com a llegit &Post a Note &Publicar una nota &View &Veure &Toolbar &Barra d'eines Full &Screen &Pantalla completa &Log &Registre S&ettings &Configuració Edit &Profile Editar &perfil &Account &Compte &Configure Dianara &Configurar Dianara &Help Aj&uda Basic &Help &Ajuda bàsica Visit &Website Visitar lloc &web Report a &Bug Informar d'un &error Pump.io User &Guide &Guia d'usuari de Pump.io List of Some Pump.io &Users Llista d'alguns &usuaris de Pump.io Pump.io &Network Status Website Web de l'estat de la &xarxa Pump.io About &Dianara Sobre &Dianara Toolbar Barra d'eines Open the log viewer Obrir el visualitzador del registre Auto-updating enabled Auto-actualitzacions activades Auto-updating disabled Auto-actualitzacions desactivades Proxy password required Contrasenya de proxy necessària You have configured a proxy server with authentication, but the password is not set. Has configurat un servidor proxy amb autenticació, pero la contrasenya no està definida. Enter the password for your proxy server: Introdueix la contrasenya pel teu servidor proxy: Your biography is empty La teva biografia està buida Click to edit your profile Fes clic per editar el teu perfil Starting automatic update of timelines, once every %1 minutes. Iniciant actualització automàtica de línies temporals, una vegada cada %1 minuts. Stopping automatic update of timelines. Aturant actualització automàtica de línies temporals. Received %1 older posts in '%2'. %1 is a number, %2 = name of a timeline S'han rebut %1 missatges anteriors a '%2'. 1 more pending to receive. singular, one post 1 més pendent de rebre. %1 more pending to receive. plural, several posts %1 més pendents de rebre. Also: A més: Last update: %1 Última actualització: %1 1 more pending to receive. singular, 1 activity 1 més pendent de rebre. %1 more pending to receive. plural, several activities %1 més pendents de rebre. '%1' updated. %1 is the name of a feed '%1' actualitzada. 1 pending to receive. singular, one post 1 pendent de rebre. %1 pending to receive. plural, several posts %1 pendents de rebre. 1 highlighted. singular, refers to a post 1 destacat. %1 highlighted. plural, refers to posts %1 destacats. 1 filtered out. singular, refers to a post 1 filtrat. %1 filtered out. plural, refers to posts %1 filtrats. 1 deleted. singular, refers to a post 1 eliminat. %1 deleted. plural, refers to posts %1 eliminats. No new posts. No hi ha missatges nous. Mentions Mencions There is 1 new activity. Hi ha 1 activitat nova. There are %1 new activities. Hi han %1 activitats noves. 1 pending to receive. singular, 1 activity 1 pendent de rebre. %1 pending to receive. plural, several activities %1 pendents de rebre. 1 highlighted. singular, refers to an activity 1 destacada. %1 highlighted. plural, refers to activities %1 destacades. 1 filtered out. singular, refers to one activity 1 filtrada. %1 filtered out. plural, several activities %1 filtrades. No new activities. No hi ha activitats noves. Error storing image! Error emmagatzemant la imatge! %1 bytes %1 bytes Link to: %1 Enllaç a: %1 Marking everything as read... Marcant tot com a llegit... With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. Amb Dianara pots veure les teves línies temporals, crear nous missatges, penjar fotos i multimèdia, interactuar amb els missatges, gestionar els teus contactes i seguir gent nova. English translation by JanKusanagi. TRANSLATORS: Change this with your language and name. If there was another translator before you, add your name after theirs ;) Traducció al català per JanKusanagi. &Hide Window &Ocultar finestra &Show Window &Mostrar finestra Closing due to environment shutting down... Tancant a causa de que l'entorn s'està aturant... Quit? Sortir? You are composing a note or a comment. Estàs redactant una nota o un comentari. Do you really want to close Dianara? Realment vols tancar Dianara? &Yes, close the program &Sí, tancar el programa &No &No Shutting down Dianara... Tancant Dianara... System tray icon is not available. La icona de la safata del sistema no està disponible. Dianara cannot be hidden in the system tray. Dianara no es pot ocultar a la safata del sistema. Do you want to close the program completely? Vols tancar el programa completament? There is 1 new post. Hi ha 1 missatge nou. There are %1 new posts. Hi han %1 missatges nous. Timeline updated. Línia temporal actualitzada. Timeline updated at %1. Línia temporal actualitzada a les %1. Total posts: %1 Total de missatges: %1 Your Pump.io account is not configured El teu compte Pump.io no està configurat Received %1 older items in '%2'. %1 is a number, %2 = name of feed S'han rebut %1 elements anteriors a '%2'. Dianara is a pump.io social networking client. Dianara es un client de xarxa social per pump.io. Thanks to all the testers, translators and packagers, who help make Dianara better! Gràcies a tots els 'testers', traductors i empaquetadors, que ajuden a fer Dianara millor! Dianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) Dianara es troba llicenciat sota la llicència GNU GPL, i utilitza algunes icones Oxygen: http://www.oxygen-icons.org/ (Llicència LGPL) The main timeline La línia temporal principal Press F1 for help Prem F1 per veure l'ajuda Click here to configure your account Fes clic aquí per configurar el teu compte Update %1 Actualitzar %1 Show Welcome Wizard Mostrar l'auxiliar de benvinguda Your own posts Els teus propis missatges Your favorited posts Els missatges que t'agraden Messages sent explicitly to you Missatges enviats explícitament a tu Update Minor &Feed meh... Actualitzar línia temporal meno&r &Filters and Highlighting hmmm &Filtres i destacats Some Pump.io &Tips Alguns &consells sobre Pump.io Favor&ites &Favorits Received %1 older activities in '%2'. %1 is a number, %2 = name of feed S'han rebut %1 activitats anteriors a '%2'. Minor feed updated at %1. Línia temporal menor actualitzada a les %1. Minor feed updated. Línia temporal menor actualitzada. About Dianara Sobre Dianara MinorFeed Older Activities Activitats anteriors Get previous minor activities Obtenir activitats menors anteriors There are no activities to show yet. Encara no hi ha activitats per mostrar. Get %1 newer As in: Get 3 newer (activities) Rebre %1 més noves MinorFeedItem Using %1 Application used to generate this activity Utilitzant %1 To: %1 1=people to whom this activity was sent Per a: %1 Cc: %1 1=people to whom this activity was sent as CC Cc: %1 CC: %1 1=people to whom this activity was sent as CC CC: %1 Open referenced post Obrir el missatge referenciat MiscHelpers bytes bytes PageSelector Jump to page Saltar a pàgina Page number: Número de pàgina: &First As in: first page &Primera &Last As in: last page Últim&a Newer As in: newer pages Més noves Older As in: older pages Més antigues &First &Primera &Last Ultim&a Newer Més noves Older Més antigues &Go &Anar Go to &last page Anar a l'última &pàgina &Cancel &Cancel·lar PeopleWidget &Search: Ce&rcar: Enter a name here to search for it Escriu aquí un nom per buscar-ho Add a contact to a list Afegir un contacte a una llista &Cancel &Cancel·lar Post Like this post Dir que t'agrada aquest missatge Like M'agrada Shared on %1 Compartit el %1 &Close &Tancar In A To Per a CC CC Using %1 1=Program used for posting or sharing Utilitzant %1 Parent As in 'Open the parent post'. Try to use the shortest word! Pare Open the parent post, to which this one replies Obrir el missatge pare, al que aquest respon Modify this post Modificar aquest missatge Join Group Unir-se al grup %1 members in the group %1 membres al grup 1 like Li agrada a 1 1 comment 1 comentari Shared %1 times Compartit %1 cops Share Compartir Click to download the attachment Fes clic per descarregar l'arxiu adjunt Post Noun, not verb Missatge Type As in: type of object Tipus Modified on %1 Modificat el %1 Edit Editar Image is animated. Click on it to play. La imatge és animada. Fes clic per reproduir-la. Loading image... Carregant imatge... Attached file Arxiu adjunt %1 likes Li agrada a %1 %1 comments %1 comentaris Delete Eliminar Via %1 Meh... A través de %1 Edited: %1 Editat: %1 Posted on %1 1=Date Publicat el %1 If you select some text, it will be quoted. Si selecciones part del text, serà citat. Unshare Deixar de compartir Unshare this post Deixar de compartir aquest missatge Open post in web browser Obrir el missatge al navegador web Cc Cc Copy post link to clipboard portapapers? Copiar l'enllaç del missatge al porta-retalls Normalize text colors Normalitzar colors del text Comment verb, for the comment button Comentar Reply to this post. Respondre a aquest missatge. Share this post with your contacts Compartir aquest missatge amb els teus contactes Erase this post Esborrar aquest missatge Couldn't load image! No s'ha pogut carregar la imatge! Attached Audio Àudio adjunt Attached Video Vídeo adjunt Attached File Arxiu adjunt %1 likes this One person Li agrada a %1 %1 like this More than one person Els hi agrada a %1 %1 shared this %1 = One person name %1 ha compartit això %1 shared this %1 = Names for more than one person %1 han compartit això Shared once Compartit un cop You like this T'agrada això Unlike Ja no m'agrada Share post? Compartir missatge? Do you want to share %1's post? Vols compartir el missatge de %1? &Yes, share it &Sí, compartir-ho &No &No Unshare post? Deixar de compartir el missatge? Do you want to unshare %1's post? Vols deixar de compartir el missatge de %1? &Yes, unshare it &Sí, deixar de compartir-ho WARNING: Delete post? hmm... advertència? AVÍS: Eliminar missatge? Are you sure you want to delete this post? Estàs segur de que vols eliminar aquest missatge? &Yes, delete it &Sí, eliminar-ho Click the image to see it in full size Fes clic a la imatge per veure-la a mida completa ProfileEditor Profile Editor Editor de perfil This is your Pump address Aquesta es la teva adreça Pump This is the e-mail address associated with your account, for things such as notifications and password recovery Aquesta és l'adreça d'e-mail associada al teu compte, per coses com ara notificacions i recuperació de la contrasenya Change &E-mail... Canviar &e-mail... Change &Avatar... Canviar &avatar... This is your visible name Aquest es el teu nom visible &Save Profile &Guardar perfil &Cancel &Cancel·lar Webfinger ID ID Webfinger E-mail E-mail Avatar Avatar Full &Name &Nom complet &Hometown Ciuta&t &Bio &Bio Not set In reference to the e-mail not being set for the account Sense definir Select avatar image Selecciona imatge d'avatar Image files Arxius d'imatge All files Tots els arxius Invalid image Imatge no vàlida The selected image is not valid. La imatge seleccionada no es vàlida. ProxyDialog Proxy Configuration Configuració de proxy Do not use a proxy No fer servir un proxy Your proxy username El teu nom d'usuari pel proxy Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. Nota: La contrasenya no s'emmagatzema de forma segura. Si ho desitges, pots deixar el camp buit, i se't demanarà la contrasenya a l'inici. &Save &Guardar &Cancel &Cancel·lar Proxy &Type &Tipus de proxy &Hostname &Servidor &Port &Port Use &Authentication Utilitzar &autenticació &User &Usuari Pass&word Con&trasenya Publisher Public Públic Followers Seguidors Picture Foto Audio Àudio Video Vídeo Ad&d... &Afegir... Upload media, like pictures or videos Pujar multimèdia, com fotos o vídeos Select Picture... Seleccionar foto... Title Títol Find the picture in your folders Trobar la foto a les teves carpetes People... Persones... Select who will see this post Selecciona qui veurà aquest missatge To... Per a... Lists Llistes CC... CC... Select who will get a copy of this post Selecciona qui rebrà una còpia d'aquest missatge Other as in other kinds of files Altres Cancel Cancel·lar Cancel the post Cancel·lar el missatge Picture not set No s'ha escollit una foto Select Audio File... Seleccionar arxiu d'àudio... Find the audio file in your folders Trobar l'arxiu d'audio a les teves carpetes Audio file not set No s'ha escollit un arxiu d'àudio Select Video... Seleccionar vídeo... Find the video in your folders Trobar el vídeo a les teves carpetes Video not set No s'ha escollit un vídeo Select File... Seleccionar arxiu... Find the file in your folders Trobar l'arxiu a les teves carpetes File not set No s'ha escollit un arxiu Error: Already composing Error: Ja s'està redactant You can't edit a post at this time, because a post is already being composed. No pots editar un missatge en aquest moment, perque ja s'està redactant un missatge. Update Actualitzar Editing post Editant missatge You can't create a message for %1 at this time, because a post is already being composed. No pots crear un missatge per a %1 en aquest moment, perque ja s'està redactant un missatge. Posting failed. Try again. Ha fallat la publicació. Torna-ho a provar. Warning: You have no followers yet Avís: Encara no tens seguidors You're trying to post to your followers only, but you don't have any followers yet. Estàs intentant publicar només per als teus seguidors, però no tens cap seguidor encara. If you post like this, no one will be able to see your message. Si publiques d'aquesta manera, ningú podrà veure el teu missatge. &Cancel, go back to the post &Cancel·lar, tornar al missatge Updating... Actualitzant... Post is empty. El missatge està buit. File not selected. No s'ha seleccionat un arxiu. Select one image Selecciona una imatge Image files Arxius d'imatge Select one file Selecciona un arxiu Invalid file Arxiu no vàlid The file type cannot be detected. El tipus d'arxiu no es pot detectar. All files Tots els arxius Since you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. Com que el que estàs pujant és una imatge, podries reduir-la una mica o guardarla en un format més comprimit, com JPG. Dianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. Actualment, Dianara limita les pujades d'arxius a 10 MiB per missatge, per a evitar possibles problemes d'emmagatzematge, o de xarxa, als servidors. This is a temporary measure, since the servers cannot set their own limits yet. Això és una mesura temporal, ja que els servidors no poden establir els seus propis límits encara. File is too big L'arxiu és massa gran Add a brief title for the post here (recommended) Afegeix un títol breu per al missatge aquí (recomanat) Do you want to make the post public instead of followers-only? Vols fer el missatge públic en comptes de només per seguidors? &Yes, make it public &Sí, fer-ho públic Sorry for the inconvenience. Lamentem les molèsties. Resolution Resolució Type Tipus Size Mida %1 KiB of %2 KiB uploaded %1 KiB de %2 KiB pujats Invalid image Imatge no vàlida Setting a title helps make the Meanwhile feed more informative Afegir un títol ajuda a fer el contingut del "Mentrestant" més informatiu Add a brief title for the post (recommended) Afegeix un títol breu per al missatge (recomanat) Remove Treure? Eliminar? Treure Cancel the attachment, and go back to a regular note Cancel·lar l'adjunt i tornar a una nota normal Cc... Cc... Post verb Publicar Note started from another application. Nota començada des d'una altra aplicació. Ignoring new note request from another application. Ignorant petició de nova nota des d'una altra aplicació. &No, post to my followers only &No, publicar només per als meus seguidors The image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. No es pot detectar el format de la imatge. Potser l'extensió està equivocada, com una imatge GIF reanomenada a imatge.jpg o semblant. Select one audio file Selecciona un arxiu d'àudio Audio files Arxius d'àudio Invalid audio file Arxiu d'àudio no vàlid The audio format cannot be detected. El format d'àudio no es pot detectar. Select one video file Selecciona un arxiu de vídeo Video files Arxius de vídeo Invalid video file Arxiu de vídeo no vàlid The video format cannot be detected. El format de vídeo no es pot detectar. Posting... Publicant... Hit Control+Enter to post with the keyboard Prem Control+Enter per publicar amb el teclat PumpController Getting likes... meh.... Rebent "likes"... Getting comments... Rebent comentaris... Getting minor feed... meh... Rebent línia temporal menor... Error connecting to %1 Error connectant a %1 Unhandled HTTP error code %1 Codi d'error HTTP no gestionat: %1 Post published successfully. Missatge publicat correctament. Comment posted successfully. Comentari publicat correctament. Minor feed received. meh... Línia temporal menor rebuda. Following %1 (%2) successfully. %1 is a person's name, %2 is the ID Seguint a %1 (%2) correctament. Stopped following %1 (%2) successfully. %1 is a person's name, %2 is the ID S'ha deixat de seguir a %1 (%2) correctament. List of 'following' completely received. Llista de 'seguint' completament rebuda. Partial list of 'following' received. Part de la llista de 'seguint' rebuda. List of 'followers' completely received. Llista de 'seguidors' completament rebuda. Partial list of 'followers' received. Part de la llista de 'seguidors' rebuda. List of %1 users received. %1 is a server name Llista d'usuaris de %1 rebuda. Person list '%1' created successfully. Llista de persones '%1' creada correctament. Person list received. Llista de persones rebuda. File uploaded successfully. Posting message... Arxiu penjat correctament. Publicant missatge... Timeline received. Updating post list... S'ha rebut la línia temporal. Actualitzant llista de missatges... Authorized to use account %1. Getting initial data. Autoritzat per fer servir el compte %1. Rebent dades inicials. There is no authorized account. No hi ha cap compte autoritzat. Getting list of 'Following'... Rebent llista de 'Seguint'... Getting list of 'Followers'... Rebent llista de 'Seguidors'... Getting site users for %1... %1 is a server name Rebent usuaris del servidor %1... Getting list of person lists... Rebent llista de llistes de persones... Creating person list... Creant llista de persones... Deleting person list... Esborrant llista de persones... Getting a person list... Rebent una llista de persones... Adding person to list... Afegint una persona a una llista... Removing person from list... Treient una persona d'una llista... Creating group... Creant grup... Joining group... Unint-se al grup... Leaving group... Sortint del grup... Main timeline update requested, but updates are blocked. Actualització de línia temporal principal sol·licitada, però les actualitzacions estan bloquejades. Getting main timeline... Rebent línia temporal principal... Direct timeline update requested, but updates are blocked. Actualització de línia temporal directa sol·licitada, però les actualitzacions estan bloquejades. Getting direct messages timeline... Rebent línia temporal de missatges directes... Activity timeline update requested, but updates are blocked. Actualització de línia temporal d'activitat sol·licitada, però les actualitzacions estan bloquejades. Getting activity timeline... Rebent línia temporal d'activitat... Favorites timeline update requested, but updates are blocked. Actualització de línia temporal de favorits sol·licitada, però les actualitzacions estan bloquejades. Getting favorites timeline... Rebent línia temporal de favorits... Timeline update requested, but updates are blocked. Actualització de línia temporal sol·licitada, però les actualitzacions estan bloquejades. Getting timeline... Rebent línia temporal... Timeline Línia temporal Messages Missatges User timeline Línia temporal d'usuari Uploading %1 1=filename Pujant %1 HTTP error For the following HTTP error codesyou can check http://en.wikipedia.org/wiki/List_of_HTTP_status_codes in your language Error HTTP Gateway Timeout HTTP 504 error string Temps d'espera de la passarel·la excedit Service Unavailable HTTP 503 error string Servei no disponible Not Implemented HTTP 501 error string No implementat Internal Server Error HTTP 500 error string Error intern Gone HTTP 410 error string Ja no disponible Not Found HTTP 404 error string No trobat Forbidden HTTP 403 error string Prohibit Unauthorized HTTP 401 error string No autoritzat Bad Request HTTP 400 error string Sol·licitud incorrecta Moved Temporarily HTTP 302 error string Mogut temporalment Moved Permanently HTTP 301 error string Mogut permanentment Profile received. Perfil rebut. Followers Seguidors Following Seguint Profile updated. Perfil actualitzat. E-mail updated: %1 E-mail actualitzat: %1 %1 published successfully. Updating post content... %1 is the type of object: note, image... S'ha publicat '%1' correctament. Actualitzant contingut del missatge... Untitled post %1 published successfully. %1 is a piece of the post Missatge sense títol %1 publicat correctament. Post %1 published successfully. %1 is the title of the post Missatge %1 publicat correctament. Avatar published successfully. Avatar publicat correctament. Untitled post %1 updated successfully. %1 is a piece of the post Missatge sense títol %1 actualitzat correctament. Post %1 updated successfully. %1 is the title of the post Missatge %1 actualitzat correctament. Comment %1 updated successfully. %1 is a piece of the comment Comentari %1 actualitzat correctament. Comment %1 posted successfully. %1 is a piece of the comment Comentari %1 publicat correctament. %1 attempts %1 intents 1 attempt 1 intent Some initial data was not received. Restarting initialization... Algunes dades inicials no s'han rebut. Reiniciant la inicialització... Post updated successfully. Missatge actualitzat correctament. Comment updated successfully. Comentari actualitzat correctament. Message liked or unliked successfully. Missatge marcat o desmarcat "M'agrada" correctament. Likes received. meh... "M'agrada" rebuts. Comment '%1' posted successfully. %1 is a piece of the comment Comentari '%1' publicat correctament. 1 comment received. 1 comentari rebut. %1 comments received. %1 comentaris rebuts. Post by %1 shared successfully. 1=author of the post we are sharing Missatge de %1 compartit correctament. Received '%1'. %1 is the name of a feed S'ha rebut '%1'. Adding items... Afegint elements... SSL errors in connection to %1! Errors d'SSL a la connexió a %1! Loading external image from %1 regardless of SSL errors, as configured... %1 is a hostname Carregant imatge externa de %1 tot i amb els errors SSL, com s'ha configurat... OAuth error while authorizing application. Error de OAuth mentre s'autoritzava a l'aplicació. Message deleted successfully. Missatge eliminat correctament. The comments for this post cannot be loaded due to missing data on the server. Els comentaris d'aquest missatge no es poden carregar a causa de que falten dades al servidor. Getting '%1'... %1 is the name of a feed Rebent '%1'... Main timeline Línia temporal principal Direct messages Missatges directes Activity Activitat Favorites Favorits Meanwhile Mentrestant Mentions Mencions Actions Accions Getting '%1'... %1 is a feed's name Rebent "%1"... List of 'lists' received. Llista de 'llistes' rebuda. Person list deleted successfully. Llista de persones esborrada correctament. %1 (%2) added to list successfully. 1=contact name, 2=contact ID S'ha afegit a %1 (%2) a la llista correctament. %1 (%2) removed from list successfully. 1=contact name, 2=contact ID S'ha eliminat a %1 (%2) de la llista correctament. Group %1 created successfully. Grup %1 creat correctament. Group %1 joined successfully. S'ha entrat al grup %1 correctament. Left the %1 group successfully. S'ha sortit del grup %1 correctament. File downloaded successfully. Arxiu descarregat correctament. Avatar uploaded. Avatar penjat. Loading embedded image from %1 regardless of SSL errors, as configured %1 is a hostname Carregant imatge incrustada des de %1 tot i amb els errors SSL, com s'ha configurat The application is not registered with your server yet. Registering... La aplicació encara no es troba registrada amb el teu servidor. Registrant... Getting OAuth token... Rebent identificador d'autorització (token) d'OAuth... OAuth support error Error de compatibilitat d'OAuth Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support. Sembla que la teva instal·lació de QOAuth, una biblioteca utilitzada per Dianara, no te compatibilitat amb HMAC-SHA1. You probably need to install the OpenSSL plugin for QCA: %1, %2 or similar. Probablement necessitis instal·lar el connector d'OpenSSL per QCA: %1, %2 o similar. Authorization error Error d'autorització There was an OAuth error while trying to get the authorization token. Hi ha hagut un error de OAuth mentre s'intentava obtenir un identificador d'autorització (token). QOAuth error %1 Error de QOAuth %1 Application authorized successfully. Aplicació autoritzada correctament. Waiting for proxy password... Esperant per la contrasenya del proxy... Still waiting for profile. Trying again... Encara s'està esperant el perfil. Intentant-ho de nou... Some initial data was not received. Restarting initialization. Algunes dades inicials no s'han rebut. Reiniciant la inicialització. Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally. Algunes dades inicials no s'han rebut després de diversos intents. Alguna cosa pot estar fallant al teu servidor. Es possible que puguis utilitzar el servei amb normalitat. All initial data received. Initialization complete. S'han rebut totes les dades inicials. Inicialització completada. Ready. Preparat. SiteUsersList You can get a list of the newest users registered on your server by clicking the button below. Pots rebre una llista dels usuaris més nous registrats al teu servidor fent clic al botó de sota. More resources to find users: Més recursos per trobar usuaris: Wiki page 'Users by language' Pàgina wiki 'Usuaris per idioma' PPump user search service at inventati.org Servei PPump de cerca d'usuaris a inventati.org Get list of users from your server Rebre llista d'usuaris del teu servidor Close list Tancar la llista Loading... Carregant... %1 users in %2 %1 = user count, %2 = server name %1 usuaris a %2 TimeLine Welcome to Dianara Benvingut a Dianara Dianara is a <b>pump.io</b> client. Dianara es un client <b>pump.io</b>. Dianara is a <b>Pump.io</b> client. Dianara és un client <b>Pump.io</b>. Press <b>F1</b> if you want to open the Help window. Prem <b>F1</b> si vols obrir la finestra d'ajuda. First, configure your account from the <b>Settings - Account</b> menu. En primer lloc, configura el teu compte des del menú <b>Configuració - Compte</b>. After the process is done, your profile and timelines should update automatically. Quan el procés estigui llest, el teu perfil i línies temporals haurien d'actualitzar-se automàticament. Take a moment to look around the menus and the Configuration window. Pren-te un moment per fer una ullada als menús i la finestra de Configuració. You can also set your profile data and picture from the <b>Settings - Edit Profile</b> menu. També pots omplir la teva informació de perfil i foto des del menú <b>Configuració - Editar perfil</b>. Dianara's blog Bloc de Dianara Newest El més nou Newer Més nous Older Més antics Requesting... Demanant... Loading... Carregant... Page %1 of %2. Pàgina %1 de %2. Showing %1 posts per page. Mostrant %1 missatges per pàgina. %1 posts in total. %1 missatges en total. Click here or press Control+G to jump to a specific page Fes clic aquí o prem Control+G per saltar a una pàgina específica '%1' cannot be updated because a comment is currently being composed. %1 = feed's name No es pot actualitzar '%1' perquè s'està editant un comentari. %1 more posts pending for next update. %1 missatges més pendents per la propera actualització. Click here to receive them now. Fes clic aquí per rebre'ls ara. There are no posts No hi ha missatges Get %1 pending posts Rebre %1 missatges pendents If you don't have a Pump account yet, you can get one at the following address, for instance: Si encara no tens un compte Pump, pots aconseguir-ne un a la següent adreça, per exemple: There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information. Hi ha indicadors de funció (tooltips) per tot arreu, pel que si mantens el ratolí sobre un botó o un camp de text, és probable que vegis alguna informació addicional. Pump.io User Guide Guia d'usuari de Pump.io Direct Messages Timeline Línia temporal de missatges directes Here, you'll see posts specifically directed to you. Aquí veuràs els missatges dirigits específicament a tu. Activity Timeline Línia temporal d'activitat You'll see your own posts here. Aquí veuràs els teus propis missatges. Favorites Timeline Línia temporal de favorits Posts and comments you've liked. Missatges i comentaris que t'han agradat. Timestamp Invalid timestamp! Hora/data no vàlida! A minute ago Fa un minut %1 minutes ago Fa %1 minuts An hour ago Fa una hora %1 hours ago Fa %1 hores Just now Ara mateix In the future En el futur Yesterday Ahir %1 days ago Fa %1 dies A month ago Fa un mes %1 months ago Fa %1 mesos A year ago Fa un any %1 years ago Fa %1 anys UserPosts Posts by %1 Missatges de %1 Loading... Carregant... &Close &Tancar Received '%1'. S'ha rebut '%1'. %1 posts %1 missatges Error loading the timeline Error carregant la línia temporal dianara-v1.3.2/translations/dianara_it.qm000644 000764 000764 00000312313 12614520547 020060 0ustar00janjan000000 000000 hv*+O+O)+O*+O\+O[+OM+OnSWE[`V9 W$+ n%EJt0 001H5ãVuv v&.ee4 %U."&(m2*E*0i+vE++v++ ,}F2>c!<~<+c@)DBjH3HHIaKMeBNFNP7P7@RS %THTpV*qFVV%WjK%WLUoXXƥ"YZy%4Q[ %4@[!4o\\5j knFq׎Nwj[yT7ybyzz>NJ5DhRmN%cKS>Gn\;"$.V"46̸*ՅE(I5 /ҥH~ 7ϗ[*Dk(NHff>^H_%dݣ Ts!$Y-!*05<#<MhDInL:M$P3vQE0RSJV8\qYlbop`N_1=# ,/ZrSh>XwF^t<AlΣ=% 2Pt #)J UQ%%Q{3G"KR RxN0hBKpH4tɥ%4v2w,Sg&Jq|W9^] Zjt{6v,6v6N66A6{B c}2 nMM!y_ƭ>NZ4jtVږϠN'c؞daF!8Y3"#l*J@! o U e vS $1"l$1:(J.ssf0cJ0cuz6"6E6'E70?O@>99EA9M.-YXR@Zvad܍i6eil#AsCex2izd}ez-y.QЁNnBV^aBaobto{¿U¿ÌoP^q!q޷?f_lN,#nz nK%?,ʳ[ 14 H#n6U>x,B(P Tj/`3a$gUMr) Y:E:] >F7 <u  U>]?I$IiYIY.lvc 6?, >,7<3nhԁ^^_cSeKmE4"Jͣ[3g3g]ր}vRn@mz-7 W]?GΈFn 1z ۬<#,,H4IKTKT`KLܤqRMBRC.V|2]L.]GOabc|fy h9~:<hTspu_y{7{'|78}G',.{YF(,VV\ h>0,4L>wT&OtDGj1:4@úXc\wd0#[0ƨ42 onZh~ziNM&أJ"*gY+-^P4uFL4u ]4u$9$(^<. <.z<.^=C@Gmb@IIM?$RVх_ZPZzuZ{f;1fDgrVhCƲmnM p{.r7%u$AyDa/z Nn SC,1{̐*]VU"4!*ґ]ڑu.ڱpz1j;ǣ&#ktC{#D0 1;3;XVk<F<u< <#Wdcg-jDlqNPprʙt'{knjo+_X<0ROrx@ȯBҕ`>GvnWaI/N>Ni"Q i?X=n:EN"#N )2K3H2?,6~~yF\ѝ]P efe7fos#(sn/|2[{|b@MU F'yOk"elB>V_dBN/wq:`rnť*]琊AQ,dR_8C !k -Qx 0\~W 0u~ 1`#sIQ | ~4 t 4 AA 62  > `y o bpV ʞ U p2q &X ~O Ԭ [@ hCz NZu Ac A ' ~jP oy  !/ #JE (O֢ ?m Hkc Tu\ TG_ TG [dg ] c$ d 4n d< d<$H d< pO/ q\- v'Yg z3.m .g 84 I I I I<> Il Im I I` I I )@ ~b I{ o ' c4 Ra LB "eI "s ,i4 -֮|h 0 5<\" <خk =/nW @w ED Ya(1 bX%B bX eT f( f*2̸ mmT o> |6/ P/z[ !C S@ #O K VT ky : b )Q/ YS " sd8 5 jb أ y < C> l 2 (=] - U * / 66 =x BP E YB [cPP ]ӡv `um {T {  H rw{q V xQ cB q; # s ͡$ =* Б*y I <m yh |SD' = w2 a0 =)  Еz #pn0 )A .> G NnfE V++ _P5= b e&K h1^ iFCD- 1[ @ | Z5 | >p ^X4 q/ ˽+0W D b x t w ` V'=3NO2AT#x+jAa/ D CR/.UR?T33XKc/gO>ylNSl>lill ulBmhnmLK$8TN8T8T>`Q{' }d\'OU~9A%*"70C0U<O-y`R #uh4`^p_^`z8Y'5%_'5%x'5%s'ET")+<͵,04A QBtnUB}~9CetFJHBPdxT=M_}/xeY.h` i2^)u/~^<nDh.^F؞v¹e7ts~o}vdzJ$"Lڨd&XޝGi|b%1 di %2%1 by %2 ASActivityPubblicoPublic ASActivityArticoloArticleASObject AudioAudioASObjectCollezione CollectionASObjectCommentoCommentASObjectEliminato il %1 Deleted on %1ASObjectFileFileASObject GruppoGroupASObjectImmagineImageASObject8Nessuna localit dettagliataNo detailed locationASObjectNotaNoteASObject AltroOtherASObject VideoVideoASObjecte %1 altri and %1 othersASObjecte nessun altro and one otherASObject CittHometownASPerson.&Autorizza Applicazione&Authorize Application AccountDialog&Cancella&Cancel AccountDialog&Salva i Dati &Save Details AccountDialog&Sblocca&Unlock AccountDialogOra si avviera il browser web, dove potrai ottenere il codice di verificaAA web browser will start now, where you can get the verifier code AccountDialog,Configurazione AccountAccount Configuration AccountDialogDopo aver cliccato questo bottone, il browser web si dovrebbe aprire, richiedendo l'autorizzazione per DianaraYAfter clicking this button, a web browser will open, requesting authorization for Dianara AccountDialogZDianara autorizzato ad acceder ai tuoi dati)Dianara is authorized to access your data AccountDialogInserisci o incolla il codice di verifica fornito dal tuo server Pump quiBEnter or paste the verifier code provided by your Pump server here AccountDialog|Prima inserisci il tuo Webfinger ID, il tuo indirizzo Pump.io.5First, enter your Webfinger ID, your pump.io address. AccountDialog<Ottieni il Codice di &VerificaGet &Verifier Code AccountDialogSe il browser non si apre automaticamente, copia questo indirizzo manualmenteEIf the browser doesn't open automatically, copy this address manually AccountDialogSe non hai ancora un account, puoi iscriverti su %1. Questo link ti porter su un server pubblico casuale.sIf you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. AccountDialog6Se hai bisogno di aiuto: %1If you need help: %1 AccountDialogSe il tuo profilo, per esempio, https://pumpserver.org/nomeutente, di conseguenza il tuo indirizzo nomeutente@pumpserver.org_If your profile is at https://pump.example/yourname, then your address is yourname@pump.example AccountDialog4Una volta autorizzato Dianara dall'interfaccia web del tuo server Pump.io, riceverai un codice chiamato VERIFIER. Copialo e incollalo nel campo qui sotto.Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. AccountDialogfPremi Sblocca se vuoi configurare un altro account.:Press Unlock if you wish to configure a different account. AccountDialog(Guida Utente Pump.ioPump.io User Guide AccountDialogNIl campo del codice di verifica vuotoVerifier code is empty AccountDialog&Codice di Verifica:Verifier code: AccountDialogJIl tuo indirizzo Pump.io non validoYour Pump address is invalid AccountDialog2Il tuo indirizzo Pump.io:Your Pump.io address: AccountDialogVIl tuo account configurato correttamente.$Your account is properly configured. AccountDialogIl tuo indirizzo assomiglia a nomeutente@pumpserver.org, e lo puoi trovare sul tuo profilo, nell'interfaccia web.kYour address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. AccountDialog`Il tuo indirizzo, tipo nomeutente@serverpump.org*Your address, like username@pumpserver.org AccountDialog0&Aggiungi ai Selezionati&Add to SelectedAudienceSelector&Cancella&CancelAudienceSelector &Fatto&DoneAudienceSelectorLista 'Cc' 'Cc' ListAudienceSelectorLista 'A' 'To' ListAudienceSelector Tutti i Contatti All ContactsAudienceSelector"Pulisci la &Lista Clear &ListAudienceSelector2Seleziona le persone dalla lista a sinistra. Puoi trascinarle con il mouse, click o doppio click su di esse, o selezionarle e usare il bottone qui sotto.:ON THE LEFT should change to ON THE RIGHT in RTL languagesSelect people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below.AudienceSelector&Persone SelezionateSelected PeopleAudienceSelector&No&No AvatarButton,&Si, smetti di seguire&Yes, stop following AvatarButtonVSei sicuro di voler smettere di seguire %1?+Are you sure you want to stop following %1? AvatarButton SeguiFollow AvatarButtonJApri il profilo di %1 nel browser web Open %1's profile in web browser AvatarButtonFApri il tuo profilo nel browser web Open your profile in web browser AvatarButton(Invia messaggio a %1Send message to %1 AvatarButton"Smetti di seguireStop following AvatarButton(Smettere di seguire?Stop following? AvatarButton CambiaChange ColorPicker4A %1 piace questo commento%1 like this commentComment4A %1 piace questo commento%1 likes this commentComment&No&NoComment&Si, cancellalo&Yes, delete itComment^Sei sicuro di voler cancellare questo commento?-Are you sure you want to delete this comment?CommentEliminaDeleteCommentModificaEditComment.Elimina questo commentoErase this commentCommentMi piaceLikeCommentbDecidi se ti piace o non ti piace questo commentoLike or unlike this commentComment Modificato il %1Modified on %1Comment0Modifica questo commentoModify this commentComment Pubblicato il %1 Posted on %1CommentQuotareQuoteCommentBRispondi quotando questo commentoReply quoting this commentCommentNon mi piaceUnlikeCommentFATTENZIONE: Cancellare il commento?WARNING: Delete comment?CommentCancellaCancelCommenterBlockCommentareCommentCommenterBlock(Il commento vuoto.Comment is empty.CommenterBlock.Modificando il commentoEditing commentCommenterBlock4Errore: stai gi scrivendoError: Already composingCommenterBlockXInvio del commento fallito. Prova di nuovo.#Posting comment failed. Try again.CommenterBlockjPremi ESC per annullare il commento, se non c' testo3Press ESC to cancel the comment if there is no textCommenterBlock"Ricarica commentiReload commentsCommenterBlock.Inviando il commento...Sending comment...CommenterBlock4Mostra tutti i %1 commentiShow all %1 commentsCommenterBlock4Aggiornando il commento...Updating comment...CommenterBlockvPremi Control+Invio per inviare il commento con la tastieraAYou can press Control+Enter to send the comment with the keyboardCommenterBlockNon puoi modificare ora il commento, perch ne stai gi scrivendo un altro.YYou can't edit a comment at this time, because another comment is already being composed.CommenterBlock&Formato&FormatComposer&No&NoComposer&Si, cancellalo&Yes, cancel itComposer`Sei sicuro di voler cancellare questo messaggio?-Are you sure you want to cancel this message?ComposerGrassettoBoldComposer0Cancellare il messaggio?Cancel message?ComposernClicca qui o premi Control+N per pubblicare una nota.../Click here or press Control+N to post a note...Composer,Errore: URL non validoError: Invalid URLComposerFormattazione FormattingComposer TitoloHeaderComposer6Quante colonne (larghezza)?How many columns (width)?Composer.Quante righe (altezza)?How many rows (height)?Composer"Inserisci un link Insert a linkComposer>Inserisci un'immagine da un URLInsert an image from a URLComposerHInserisci un'immagine da un sito webInsert an image from a web siteComposer.Inserire come immagine?Insert as image?Composer6Inserisci come collegamentoInsert as linkComposer@Inserisci come immagine visibileInsert as visible imageComposer&Inserisci una linea Insert lineComposerCorsivoItalicComposer ListaListComposerCrea un link Make a linkComposerJCrea un link con il testo selezionatoMake a link from selected textComposerNormaleNormalComposerBIncolla testo senza formattazionePaste Text Without FormattingComposer(Blocco preformattatoPreformatted blockComposer Blocco citazioni Quote blockComposerBarrato StrikethroughComposerSimboliSymbolsComposerTabellaTableComposer$Dimensioni Tabella Table SizeComposerDOpzioni di formattazione del testoText Formatting OptionsComposerL'indirizzo inserito (%1) non valido. Gli indirizzi di un'immagine dovrebbero iniziare con http:// o https://`The address you entered (%1) is not valid. Image addresses should begin with http:// or https://ComposerIl collegamento che stai incollando sembra puntare ad un'immagine.4The link you are pasting seems to point to an image.Composer,Scrivi un commento quiType a comment hereComposerNScrivi qui un messaggio per pubblicarloType a message here to post itComposerDigita o incolla un indirizzo web qui. Il testo selezionato (%1) sar convertito in un link.UType or paste a web address here. The selected text (%1) will be converted to a link.ComposerScrivi o incolla un indirizzo web qui. Puoi anche selezionare del testo, per convertirlo poi in un link.`Type or paste a web address here. You could also select some text first, to turn it into a link.ComposerScrivi o incolla l'indirizzo dell'immagine qui. Il link deve puntare direttamente all'immagine.UType or paste the image address here. The link must point to the image file directly.ComposerSottolineato UnderlineComposer&Cancella&Cancel ConfigDialog&Tab mobili &Movable tabs ConfigDialogR&Elementi per pagina, timeline principale&Posts per page, main timeline ConfigDialog*&Salva Configurazione&Save Configuration ConfigDialog(Posizione delle &tab&Tabs position ConfigDialogTutti i files All files ConfigDialog SempreAlways ConfigDialog<Qualsiasi attivit selezionataAny highlighted activity ConfigDialog2Come notifiche di sistemaAs system notifications ConfigDialog"Dimensione avatar Avatar size ConfigDialogIn bassoBottom ConfigDialog ColoriColors ConfigDialogCommentiComments ConfigDialogComponiComposer ConfigDialog*&Icona Personalizzata Custom &Icon ConfigDialog(Icona personalizzata Custom icon ConfigDialogDefaultDefault ConfigDialogPDianara salva i dati in questa cartella:#Dianara stores data in this folder: ConfigDialog,Non mostrare notificheDon't show notifications ConfigDialog(Regole di filtraggioFiltering rules ConfigDialogFontFonts ConfigDialog Opzioni GeneraliGeneral Options ConfigDialog.Nascondi post duplicatiHide duplicated posts ConfigDialogREvidenzia i commenti dell'autore del post Highlight post author's comments ConfigDialog0Evidenza i tuoi commentiHighlight your own comments ConfigDialogPAttivit in evidenza nel feed secondario$Highlighted activities in minor feed ConfigDialogHAttivit selezionate, escluse le mie#Highlighted activities, except mine ConfigDialog Post in evidenzaHighlighted posts ConfigDialogFile immagine Image files ConfigDialog&Immagine non valida Invalid image ConfigDialogfOggetto evidenziato in base alle regole dei filtri.(Item highlighted due to filtering rules. ConfigDialogNuovo Elemento. Item is new. ConfigDialogA sinistra(tabs on left side/west; RTL not affected Left side ConfigDialogFeed Minori Minor Feeds ConfigDialog,Configurazione di ReteNetwork configuration ConfigDialogMaiNever ConfigDialogDNuove attivit nel feed secondarioNew activities in minor feed ConfigDialogNuovi post New posts ConfigDialog"Stile di notificaNotification Style ConfigDialogNotifiche Notifications ConfigDialog.Notifica quando ricevi:Notify when receiving: ConfigDialog$Contenuto dei post Post Contents ConfigDialogTitoli dei Post Post Titles ConfigDialogPostPosts ConfigDialogHElementi per pagina, &altre timeline Posts per page, &other timelines ConfigDialog.Impostazioni del Pro&xyPro&xy Settings ConfigDialog8Configurazione del programmaProgram Configuration ConfigDialog>Messaggi pubblici come &defaultPublic posts as &default ConfigDialogA destra)tabs on right side/east; RTL not affected Right side ConfigDialogS&eleziona... S&elect... ConfigDialogBSeleziona un'icona personalizzataSelect custom icon ConfigDialogImposta F&iltriSet Up F&ilters ConfigDialog:Mostra contatore di caratteriShow character counter ConfigDialogTMostra informazioni di condivisione esteseShow extended share information ConfigDialog2Mostra informazioni extraShow extra information ConfigDialog>Mostra snippets nei feed minoriShow snippets in minor feeds ConfigDialog8Mostra il tuo avatar attualeShow your current avatar ConfigDialogLimite Snippet Snippet limit ConfigDialog Icone di Sistema System Tray ConfigDialog2&Tipo di Icona di SistemaSystem Tray Icon &Type ConfigDialog@Icone di sistema, se disponibiliSystem iconset, if available ConfigDialogL'attivit in risposta a qualcosa fatto da te, come un commento postato in risposta a una delle tue note.jThe activity is in reply to something done by you, such as a comment posted in reply to one of your notes. ConfigDialogL'attivit legata a uno dei tuoi oggetti, per esempio a qualcuno piace un tuo post.YThe activity is related to one of your objects, such as someone liking one of your posts. ConfigDialogHL'immagine selezionata non valida. The selected image is not valid. ConfigDialogVIntervallo di &aggiornamento della timelineTimeline &update interval ConfigDialogTimeline Timelines ConfigDialogIn altoTop ConfigDialogUsa il nome del file dell'allegato come titolo iniziale del post-Use attachment filename as initial post title ConfigDialogUsato anche quando i post in evidenza sono indirizzati a te nelle timeline.DUsed also when highlighting posts addressed to you in the timelines. ConfigDialognUsato anche per evidenziare i tuoi post nelle timeline.Apri il profilo nel browser webOpen Profile in Web Browser ContactCardInvia Messaggio Send Message ContactCard"Smetti di seguireStop Following ContactCard(Smettere di seguire?Stop following? ContactCardDQuesto utente non ha una biografia"This user doesn't have a biography ContactCardAggiornato: %1 Updated: %1 ContactCardOpzioni utente User Options ContactCardLista Com&pleta F&ull List ContactListzScrivi parzialmente un nome o l'ID per trovare un contatto....Type a partial name or ID to find a contact... ContactListD&Inserisci l'indirizzo da seguire:&Enter address to follow:ContactManager &Segui&FollowContactManager &Liste&ListsContactManager"Esporta FollowersExport FollowersContactManager"Esporta FollowingExport FollowingContactManagerVEsporta la lista dei 'Followers' in un file$Export list of 'followers' to a fileContactManagerVEsporta la lista dei 'Following' in un file$Export list of 'following' to a fileContactManagerFollo&wers Follo&wersContactManagerFoll&owing Followin&gContactManager$Ricarica FollowersReload FollowersContactManager$Ricarica FollowingReload FollowingContactManagerRicarica Liste Reload ListsContactManagerznomeutente@pumpserver.org o https://pumpserver.org/nomeutente2username@server.org or https://server.org/usernameContactManager %1 KiB scaricati%1 KiB downloadedDownloadWidget&No, continua &No, continueDownloadWidget&Si, ferma &Yes, stopDownloadWidget"Fermare download?Abort download?DownloadWidgetTutti i files All filesDownloadWidgetAnnullaCancelDownloadWidgetVVuoi fermare il download del file allegato?2Do you want to stop downloading the attached file?DownloadWidgetScaricaDownloadDownloadWidget Download fermatoDownload abortedDownloadWidget&Download completatoDownload completedDownloadWidget Download fallitoDownload failedDownloadWidget(Scaricando %1 KiB...Downloading %1 KiB...DownloadWidget$Salva File Come...Save File As...DownloadWidgetRSalva il file allegato nelle tue cartelle&Save the attached file to your foldersDownloadWidget&Cancella&Cancel EmailChanger CambiaChange EmailChanger*%1 se %2 contiene: %3%1 if %2 contains: %3 FilterEditor &Aggiungi Filtro &Add Filter FilterEditor&Annulla&Cancel FilterEditor&Nuovo Filtro &New Filter FilterEditor:&Rimuovi i Filtri Selezionati&Remove Selected Filter FilterEditor&Salva Filtri &Save Filters FilterEditor2Descrizione dell'attivitActivity Description FilterEditorApplicazione Application FilterEditorID dell'autore Author ID FilterEditorFilt&ri SalvatiC&urrent Filters FilterEditorModifica Filtri Filter Editor FilterEditorFiltri in usoFilters in use FilterEditorQui puoi impostare regole per nascondere o mettere in evidenza le attivit. Puoi filtrare per contenuto, autore o applicazione. Per esempio, puoi filtrare messaggi postati dall'applicazione Open Farm Game, o quelli che contengono la parola NSFW nel messaggio. Puoi anche evidenziare i messaggi che contengono il tuo nome.-Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. FilterEditorNascondiHide FilterEditorEvidenzia Highlight FilterEditor Parole chiave... Keywords... FilterEditor$Contenuto del post Post Contents FilterEditorcontienecontains FilterEditorseif FilterEditor&Chiudi&CloseFirstRunWizard CambiaChange FontPicker&Chiudi&Close HelpWidgetLa timeli delle attivit, dove vedrai i tuoi post e i post che hai condiviso.KActivity timeline, where you'll see your own posts, or posts shared by you. HelpWidgetA questo punto il tuo profilo, le liste contatti e le timeline saranno caricate.HAt this point, your profile, contact lists and timelines will be loaded. HelpWidgetAiuto di Base Basic Help HelpWidget6Oltre a quello, puoi usare:Besides that, you can use: HelpWidgetScegline uno con i tasti freccia e premi Invio per completare un nome. Questo aggiunger quella persona alla lista di destinatari.vChoose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. HelpWidget4Opzioni da riga di comandoCommand line options HelpWidgetContenutiContents HelpWidget\Control+1/2/3 per spostarti tra i feed minori.0Control+1/2/3 to switch between the minor feeds. HelpWidgetControl+Invio per terminare la creazione della lista di destinatari per il post, per le liste 'A' o 'Cc'.\Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. HelpWidgetControl+Invio per inviare il post quando hai terminato di comporre la nota o il commento. Se la nota vuota, puoi cancellarla premendo ESC.Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. HelpWidgetzControl+G va in qualsiasi pagina della timeline direttamente.5Control+G to go to any page in the timeline directly. HelpWidgetControl+Sinistra/Destra per saltare da una pagina all'altra nella timeline.4Control+Left/Right to jump one page in the timeline. HelpWidgetControl+Su/Gi/PgSu/PgGi/Home/Fine per muoverti nelle timeline.AControl+Up/Down/PgUp/PgDown/Home/End to move around the timeline. HelpWidgetLa timeline dei preferiti, dove vedrai i post e i commenti che ti piacciono. Pu essere usata come sistema di bookmark.pFavorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. HelpWidgetPer iniziareGetting started HelpWidget,Qui puoi anche attivare l'opzione di pubblicare sempre i tuoi post pubblici per default. Puoi sempre cambiare questa preferenza al momento di postare.Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. HelpWidgetSe aggiungi specifiche persone alla lista A, riceveranno una notifica del tuo messaggio nella tab dei messaggi diretti.kIf you add a specific person to the 'To' list, they will receive your message in their direct messages tab. HelpWidgetpSe sei nuovo su Pump.io, dai uno sguardo a questa guida:4If you're new to Pump.io, take a look at this guide: HelpWidgetAllegare immagini, video o audio ed altri file come PDF ora possibile.cIt is possible to attach images, audio, video, and general files, like PDF documents, to your post. HelpWidget`Tieni presente che ci sono molti posti in Dianara dove puoi ottenere pi informazioni fermandoti sopra alcuni testi o bottoni col mouse, e aspettare che il suggerimento appaia.Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. HelpWidget*Controlli da TastieraKeyboard controls HelpWidget$Gestire i contattiManaging contacts HelpWidget*La timeline dei messaggi, dove vedrai messaggi inviati a te specificatamente. Questi messaggi potrebbero essere stati inviati anche ad altre persone.Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. HelpWidget Nuovi messaggi appaiono evidenziati in un colore diffeerente. Puoi segnarli come gi letti cliccando in una parte vuota del messaggio.New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. HelpWidgetPostarePosting HelpWidget(Guida Utente Pump.ioPump.io User Guide HelpWidgetImpostazioniSettings HelpWidgetLa quinta timeline la timeline minore, conosciuta anche come Meanwhile (nel frattempo). Questa visibile a sinistra, ma pu anche essere nascosta. Qui vedrai attivit di tutte le persone che segui, come commenti, mi piace e segui persone.6LEFT SIDE should change to RIGHT SIDE on RTL languagesThe fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. HelpWidgetvLa prima volta che fai partire Dianara, dovresti vedere la finestra di Configurazione dell'Account. Inserisci il tuo indirizzo Pump.io come nome@server e premi Ottieni Codice di Verifica.The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. HelpWidgetLa timeline principale, dove puoi vedere tutti gli elementi postati o condivisi da persone che segui.\The main timeline, where you'll see all the stuff posted or shared by the people you follow. HelpWidgetLe azioni pi comuni che si trovano nei men hanno scorciatorie da tastiera scritte vicino a loro, come F5 o Control+N.nThe most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. HelpWidgetLa sesta e la settima timeline sono timeline minori o secondarie, simili a "Nel Frattempo", ma contiene solo attivit indirizzate direttamente a te (Menzioni) e attivit create da te (Azioni).The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). HelpWidgetPoi, il tuo browser predefinito dovrebbe caricare la pagina di autorizzazione nel tuo server Pump.io. Qui, dovrai copiare il VERIFIER code, e incollarlo nel secondo campo di Dianara. Quindi premi Autorizza Applicazione e, una volta confermato, premi Salva Dettagli.Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. HelpWidget.Ci sono sette timeline:There are seven timelines: HelpWidgetQui puoi anche gestire liste di persone, usate principalmente per inviare post a specifici gruppi.`There, you can also manage person lists, used mainly to send posts to specific groups of people. HelpWidgetpQueste attivit potrebbero avere un '+' vicino. Premilo per aprire il post al quale sono riferite. Anche qui potrai avere ulteriori informazioni se rimani fermo col mouse sul pulsante.These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. HelpWidgetTimeline Timelines HelpWidgetUsa il parametro --debug per avere informazioni extra nel tuo terminale, su quello che il programma sta facendo.mUse the --debug parameter to have extra information in your terminal window, about what the program is doing. HelpWidget,Mentre componi una nota, premi Invio per saltare dal titolo al messaggio. Premendo la freccia Su mentre sei all'inizio del messaggio, torni al titolo.While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. HelpWidgetPuoi anche inviare un messaggio diretto (inizialmente privato) al contatto da questo menu.VYou can also send a direct message (initially private) to that contact from this menu. HelpWidgetPuoi anche digitare '@' e il primo carattere del nome di un contatto per far apparire un popup con le scelte corrispondenti.wYou can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. HelpWidgetPuoi cliccare su qualsiasi avatar nei post, nei commenti, e nella timeline laterale e comparir un men con diverse opzioni, una di queste serve per seguire o smettere di seguire quella persona.You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. HelpWidgetPuoi configurare diverse cose secondo le tue preferenze, come il tempo di aggiornamento delle timeline, quanti post per pagina vuoi vedere, colori di evidenziazione, notifiche o come visualizzare l'icona di sistema.You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. HelpWidgetPuoi creare messaggi privati aggiungendo specifiche persone a queste liste e rimuovendo Followers e Pubblico.~You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. HelpWidgetPuoi trovare una lista con alcuni utenti Pump.io ed altre informazioni qui:GYou can find a list with some Pump.io users and other information here: HelpWidgetPuoi postare note cliccando il campo di testo in alto oppure premendo Control+N. Impostare un titolo per il post opzionale, ma caldamente consigliato, perch aiuta gli altri a identificarlo nella timeline laterale, nelle notifiche mail, ecc.You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. HelpWidgetPuoi vedere le liste di persone che segui e che ti seguono dalla tab Contatti.UYou can see the lists of people you follow, and who follow you from the Contacts tab. HelpWidgetPuoi selezionare chi vedr il tuo post usando i pulsanti A e Cc.EYou can select who will see your post by using the To and Cc buttons. HelpWidgetPuoi usare il parametro --config per avviare il programma con una configurazione differente. Questo pu essere utile per usare due o pi account. Puoi anche avviare due istanze di Dianara contemporaneamente.You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. HelpWidgetPuoi usare il pulsante Formattazione per formattare il tuo testo, per esempio renderlo grassetto o corsivo. Alcune di queste opzioni richiedono che del testo sia selezionato prima che siano usate.You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. HelpWidget2Dovresti dare uno sguardo alla finestra di Configurazione del Programma, sotto Impostazioni - Configura Dianara. Ci sono diverse opzioni interessanti l.You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. HelpWidget&Chiudi&Close ImageViewer$&Replay Animazione&Restart Animation ImageViewer&Salva come... &Save As... ImageViewerTutti i Files All files ImageViewer*Chiudi Visualizzatore Close Viewer ImageViewerVErrore durante il salvataggio dell'immagineError saving image ImageViewerImmagineImage ImageViewerFiles Immagine Image files ImageViewer,Salva Immagine Come...Save Image As... ImageViewer.Salvataggio Immagine... Save Image... ImageViewerC' stato un problema salvando %1. L'estensione del file dovrebbe essere .jpg o .png.UThere was a problem while saving %1. Filename should end in .jpg or .png extensions. ImageViewer(&Aggiungi alla Lista &Add to List ListsManager<&Cancella la Lista Selezionata&Delete Selected List ListsManager&No&No ListsManager&Rimuovi Membro&Remove Member ListsManager&Si&Yes ListsManager&Si, cancellala&Yes, delete it ListsManager Aggiungi Mem&bro Add Mem&ber ListsManager*Aggiungi Nuova &Lista Add New &List ListsManagerDSei sicuro di voler cancellare %1?#Are you sure you want to delete %1? ListsManager`Sei sicuro di voler rimuovere %1 dalla lista %2?4Are you sure you want to remove %1 from the %2 list? ListsManagerCrea L&ista Create L&ist ListsManager MembriMembers ListsManagerNomeName ListsManagerBRimuovere la persona dalla lista?Remove person from list? ListsManagerHDigita un nome per la nuova lista...Type a name for the new list... ListsManagerHDigita una descrizione opzionale qui!Type an optional description here ListsManager@ATTENZIONE: Cancellare la lista?WARNING: Delete list? ListsManager&Chiudi&Close LogViewerPulisci &Log Clear &Log LogViewerLogLog LogViewer%1 cancellati. %1 deleted. MainWindow%1 filtrati.%1 filtered out. MainWindow%1 filtrati.plural, refers to posts%1 filtered out. MainWindow%1 evidenziati.%1 highlighted. MainWindow%1 evidenziati.plural, refers to activities%1 highlighted. MainWindow4%1 in attesa di ricezione.%1 more pending to receive. MainWindow4%1 in attesa di ricezione.plural, several activities%1 more pending to receive. MainWindowA&ccount&Account MainWindow&Attivit &Activity MainWindow$Configura &Dianara&Configure Dianara MainWindow&Contatti &Contacts MainWindow0&Filtri e Evidenziazione&Filters and Highlighting MainWindow Ai&uto&Help MainWindow$&Nascondi Finestra &Hide Window MainWindow&Log&Log MainWindow&Messaggi &Messages MainWindow&No&No MainWindow$&Pubblica una nota &Post a Note MainWindow &E&sci&Quit MainWindow&Sessione&Session MainWindow&&Mostra la finestra &Show Window MainWindowT&imeline &Timeline MainWindow,Barra degli Strumen&ti&Toolbar MainWindow &Visualizzazione&View MainWindow0&Si, chiudi il programma&Yes, close the program MainWindow '%1' aggiornato. '%1' updated. MainWindow1 cancellato. 1 deleted. MainWindow1 filtrato.1 filtered out. MainWindow1 filtrato.singular, refers to a post1 filtered out. MainWindow1 evidenziato.1 highlighted. MainWindow1 evidenziato.singular, refers to an activity1 highlighted. MainWindow21 in attesa di ricezione.1 more pending to receive. MainWindow21 in attesa di ricezione.singular, 1 activity1 more pending to receive. MainWindow2Informazioni su &Dianara About &Dianara MainWindow.Informazioni su Dianara About Dianara MainWindow.Auto-aggiorna &TimelineAuto-update &Timelines MainWindow>Autp-aggiornamenti disabilitatiAuto-updating disabled MainWindow8Auto-aggiornamenti abilitatiAuto-updating enabled MainWindowAi&uto di Base Basic &Help MainWindowHClicca per modificare il tuo profiloClick to edit your profile MainWindowDianara non pu essere nascosto nella barra delle notifiche di sistema.,Dianara cannot be hidden in the system tray. MainWindowdDianara un client per il social network pump.io..Dianara is a pump.io social networking client. MainWindowDianara distribuita su licenza GNU GPL, ed utilizza alcune icone Oxygen: http://www.oxygen-icons.org/ (licenza LGPL)wDianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) MainWindow$Dianara partito.Dianara started. MainWindow@Vuoi veramente chiudere Dianara?$Do you really want to close Dianara? MainWindowRVuoi chiudere completamente il programma?,Do you want to close the program completely? MainWindow"Modifica &profilo Edit &Profile MainWindowTraduzione italiana a cura di Howcanuhavemyusername (or Metal Biker) (howcanuhavemyusername@microca.st).#English translation by JanKusanagi. MainWindowVInserisci la password del tuo server proxy:)Enter the password for your proxy server: MainWindowPrefer&iti Favor&ites MainWindowSc&hermo Intero Full &Screen MainWindow"Inizializzando...Initializing... MainWindow0Ultimo aggiornamento: %1Last update: %1 MainWindowLink a: %1 Link to: %1 MainWindow@Elenco di alcuni &Utenti Pump.ioList of Some Pump.io &Users MainWindow,Segna tutti come lettiMark All as Read MainWindowHMessaggi inviati esplicitamente a teMessages sent explicitly to you MainWindow@Attivit minori indirizzate a te!Minor activities addressed to you MainWindowtAttivit minori fatte da chiunque, come rispondere ai postStarting automatic update of timelines, once every %1 minutes. MainWindow&Barra di stato Status &Bar MainWindow`Ferma l'aggiornamento automatico delle timeline.'Stopping automatic update of timelines. MainWindowJL'icona di sistema non disponibile."System tray icon is not available. MainWindowGrazie a tutti i tester, i traduttori e i manutentori dei pacchetti, che hanno aiutato Dianara a migliorare!SThanks to all the testers, translators and packagers, who help make Dianara better! MainWindow&Timeline principaleThe main timeline MainWindowLe persone che segui, quelle che ti seguono, e le tue liste di personeEThe people you follow, the ones who follow you, and your person lists MainWindow4Ci sono %1 nuove attivit.There are %1 new activities. MainWindow4Ci sono %1 nuovi messaggi.There are %1 new posts. MainWindow.C' una nuova attivit.There is 1 new activity. MainWindow,C' 1 nuovo messaggio.There is 1 new post. MainWindow8Timeline aggiornata alle %1.Timeline updated at %1. MainWindow*Barra degli StrumentiToolbar MainWindowPost totali: %1Total posts: %1 MainWindowAggiorna %1 Update %1 MainWindow&Visita il sito &webVisit &Website MainWindowLCon Dianara puoi vedere le tue timeline, creare nuovi post, caricare immagini e altri media, interagire con post, organizzare i tuoi contatti e seguire nuove persone.With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. MainWindowNStai componendo una nota o un commento.&You are composing a note or a comment. MainWindowHai configurato un server proxy con autenticazione, ma la password non impostata.TYou have configured a proxy server with authentication, but the password is not set. MainWindowPIl tuo account Pump.io non configurato&Your Pump.io account is not configured MainWindow\Il tuo account non ancora stato configurato.#Your account is not configured yet. MainWindow0La tua biografia vuotaYour biography is empty MainWindow2I tuoi messaggi preferitiYour favorited posts MainWindowI tuoi messaggiYour own posts MainWindow Scarica %1 nuove Get %1 newer MinorFeed6Ottieni attivit precedentiGet previous minor activities MinorFeed(Attivit pi vecchieOlder Activities MinorFeedPNon ci sono ancora attivit da mostrare.$There are no activities to show yet. MinorFeed Cc: %1Cc: %1 MinorFeedItem0Apri l'elemento relativoOpen referenced post MinorFeedItem A: %1To: %1 MinorFeedItem Via %1Using %1 MinorFeedItem bytesbytes MiscHelpers&Cancella&Cancel PageSelector&Vai&Go PageSelector"Salta alla pagina Jump to page PageSelectorPi recentiAs in: newer pagesNewer PageSelectorPi vecchiAs in: older pagesOlder PageSelectorPagina numero: Page number: PageSelector&Cancella&Cancel PeopleWidget&Cerca:&Search: PeopleWidgetBAggiungi un contatto ad una listaAdd a contact to a list PeopleWidgetDInserisci qui un nome per cercarlo"Enter a name here to search for it PeopleWidget%1 commenti %1 commentsPost4A %1 piace questo elemento %1 like thisPost%1 mi piace%1 likesPost4A %1 piace questo elemento %1 likes thisPost(%1 membri nel gruppo%1 members in the groupPost>%1 ha condiviso questo elemento%1 shared thisPostD%1 hanno condiviso questo elemento#%1 = Names for more than one person%1 shared thisPost&Chiudi&ClosePost&No&NoPost&Si, eliminalo&Yes, delete itPost &Si, condividilo&Yes, share itPost*&Si, non condividerlo&Yes, unshare itPost1 commento 1 commentPost1 mi piace1 likePost^Sei sicuro di voler eliminare questo messaggio?*Are you sure you want to delete this post?PostAudio allegatoAttached AudioPostFile allegato Attached FilePostVideo allegatoAttached VideoPostCcCcPostpClicca l'immagine per vederla nelle dimensioni originali&Click the image to see it in full sizePost>Clicca pe rscaricare l'allegato Click to download the attachmentPostCommentaCommentPostPCopia il link al messaggio negli appuntiCopy post link to clipboardPostEliminaDeletePostHVuoi condividere il messaggio di %1?Do you want to share %1's post?PostfVuoi rimuovere la condivisione dell'elemento di %1?!Do you want to unshare %1's post?PostModificaEditPostModificato: %1 Edited: %1Post(Cancella questo postErase this postPostLSe selezioni del testo, verr quotato.+If you select some text, it will be quoted.PostXL'immagine animata. Clicca per riprodurre.'Image is animated. Click on it to play.PostInInPost$Unisciti al Gruppo Join GroupPostMi piaceLikePost@D che ti piace questo messaggioLike this postPost.Caricando l'immagine...Loading image...Post Modificato il %1Modified on %1Post(Modifica questo postModify this postPost:Normalizza i colori del testoNormalize text colorsPostBApri il messaggio nel browser webOpen post in web browserPostpApri il post padre, quello al quale questo post risponde/Open the parent post, to which this one repliesPost PadreParentPostPubblicaPostPost Pubblicato il %1 Posted on %1Post.Rispondi a questo post.Reply to this post.PostCondividiSharePost.Condividi il messaggio? Share post?PostRCondividi questo post con i tuoi contatti"Share this post with your contactsPost$Condiviso %1 volteShared %1 timesPostCondiviso su %1 Shared on %1Post&Condiviso una volta Shared oncePostAToPostTipoTypePost Non mi piace piUnlikePost(Rimuovi condivisioneUnsharePostZRimuovere la condivisione di questo elemento? Unshare post?PostTRimuovi la condivisione di questo elementoUnshare this postPost Via %1Using %1Post Via %1Via %1PostFATTENZIONE: Eliminare il messaggio?WARNING: Delete post?PostTi piace You like thisPost&Bio&Bio ProfileEditor&Cancella&Cancel ProfileEditor Ci&tt &Hometown ProfileEditor&Salva profilo &Save Profile ProfileEditorTutti i files All files ProfileEditor AvatarAvatar ProfileEditor"Cambia &avatar...Change &Avatar... ProfileEditor"Cambia &e-mail...Change &E-mail... ProfileEditor E-mailE-mail ProfileEditor&Nome Completo Full &Name ProfileEditorFiles immagine Image files ProfileEditor&Immagine non valida Invalid image ProfileEditorNon impostataNot set ProfileEditor$Editor del profiloProfile Editor ProfileEditor<Scegli l'immagine per l'avatarSelect avatar image ProfileEditorHL'immagine selezionata non valida. The selected image is not valid. ProfileEditorQuesta l'indirizzo e-mail associato al tuo accout, per inviare notifiche e recuperare la passwordoThis is the e-mail address associated with your account, for things such as notifications and password recovery ProfileEditorBQuesto il tuo indirizzo Pump.ioThis is your Pump address ProfileEditor:Questo il tuo nome visibileThis is your visible name ProfileEditorWebfinger ID Webfinger ID ProfileEditor&Annulla&Cancel ProxyDialog&Hostname &Hostname ProxyDialog &Porta&Port ProxyDialog &Salva&Save ProxyDialog&Utente&User ProxyDialog$Non usare un proxyDo not use a proxy ProxyDialogNota: la password non salvata in modo sicuro. Se lo desideri, puoi lasciare questo campo vuoto e ti verr chiesta la password all'avvio.Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. ProxyDialogPass&word Pass&word ProxyDialog&Tipo di Proxy Proxy &Type ProxyDialog(Configurazione ProxyProxy Configuration ProxyDialog&Usa Autenti&cazioneUse &Authentication ProxyDialog2Il tuo username del proxyYour proxy username ProxyDialog2%1 KiB di %2 KiB caricati%1 KiB of %2 KiB uploaded Publisher0&Annulla e torna al post&Cancel, go back to the post PublisherV&No, rendilo visibile solo ai miei follower&No, post to my followers only Publisher*&Si, rendilo pubblico&Yes, make it public PublisherAggiun&gi...Ad&d... PublisherfAggiungi un breve titolo per il post (raccomandato)1Add a brief title for the post here (recommended) PublisherTutti i files All files Publisher AudioAudio Publisher:Il file audio non impostatoAudio file not set PublisherFiles audio Audio files PublisherCancellaCancel PublisherVRimuovi l'allegato e torna al post regolare4Cancel the attachment, and go back to a regular note Publisher*Cancella il messaggioCancel the post Publisher Cc...Cc... PublisherDianara limita la dimensione dei file caricati a 10MiB per post, in modo da prevenire problemi di spazio e rete dei server.yDianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. PublisherVuoi rendere il post pubblico invece che renderlo visibile solo ai tuoi followers?>Do you want to make the post public instead of followers-only? Publisher*Modifica il messaggio Editing post Publisher4Errore: stai gi scrivendoError: Already composing Publisher$File troppo grandeFile is too big Publisher*File non selezionato.File not selected. Publisher$File non impostato File not set PublisherLCerca il file audio nelle tue cartelle#Find the audio file in your folders Publisher@Trova il file nelle tue cartelleFind the file in your folders PublisherFTrova l'immagine nelle tue cartelle Find the picture in your folders PublisherLCerca il file video nelle tue cartelleFind the video in your folders PublisherFollowers Followers PublisherdPremi Control+Invio per pubblicare con la tastiera+Hit Control+Enter to post with the keyboard PublisherSe posti con queste impostazioni, nessuno sar in grado di leggere il tuo messaggio.?If you post like this, no one will be able to see your message. PublisherFiles immagine Image files Publisher*File audio non validoInvalid audio file PublisherFile non valido Invalid file Publisher&Immagine non valida Invalid image Publisher*File video non validoInvalid video file Publisher ListeLists Publisher AltroOther PublisherPersone... People... PublisherImmaginePicture Publisher0Immagine non selezionataPicture not set PublisherPubblicaPost Publisher*Il messaggio vuoto.Post is empty. Publisher>Invio fallito. Prova di nuovo.Posting failed. Try again. PublisherPubblicando... Posting... PublisherPubblicoPublic PublisherRimuoviRemove PublisherRisoluzione Resolution Publisher.Seleziona File Audio...Select Audio File... Publisher"Seleziona File...Select File... Publisher2Selezionare l'immagine...Select Picture... Publisher$Seleziona Video...Select Video... Publisher.Seleziona un file audioSelect one audio file Publisher"Seleziona un fileSelect one file Publisher$Scegli un'immagineSelect one image Publisher.Seleziona un file videoSelect one video file PublisherbScegli chi ricever una copia di questo messaggio'Select who will get a copy of this post PublisherHScegli chi potr vedere il messaggioSelect who will see this post PublishertImpostare un titolo rende il feed laterale pi informativo>Setting a title helps make the Meanwhile feed more informative PublisherVisto che stai caricando un'immagine, potresti ridurre la sua risoluzione o salvarla in un formato pi compresso, come JPG.sSince you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. PublisherDimensioniSize Publisher6Ci scusiamo per il disagio.Sorry for the inconvenience. PublisherdIl formato del file audio non pu essere rilevato.$The audio format cannot be detected. PublisherXIl tipo di file non pu essere identificato.!The file type cannot be detected. PublisherIl formato dell'immagine non stato riconosciuto. L'estensione pu essere sbagliata, come un'immagine GIF rinominata in immagine.jpg o altro.tThe image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. PublisherdIl formato del file video non pu essere rilevato.$The video format cannot be detected. PublisherQuesta una misura temporanea, in quanto i server non possono ancora impostare i loro limiti.OThis is a temporary measure, since the servers cannot set their own limits yet. Publisher TitoloTitle PublisherA...To... PublisherTipoType PublisherAggiornaUpdate PublisherAggiornando... Updating... PublisherFCarica media, come immagini o video%Upload media, like pictures or videos Publisher VideoVideo PublisherFiles Video Video files Publisher&Video non impostato Video not set Publisher@Avviso: non hai ancora followers"Warning: You have no followers yet PublisherNon puoi creare un messaggio per %1 al momento, perche un post gi in fase di composizione.YYou can't create a message for %1 at this time, because a post is already being composed. PublisherNon puoi modificare il messaggio ora, perch stai gi scrivendo un messaggio.MYou can't edit a post at this time, because a post is already being composed. PublisherStai cercando di postare solo per i tuoi followers, ma non ne hai ancora.SYou're trying to post to your followers only, but you don't have any followers yet. PublisherR%1 (%2) aggiunto alla lista con successo.#%1 (%2) added to list successfully.PumpControllerR%1 (%2) rimosso dalla lista con successo.'%1 (%2) removed from list successfully.PumpController*%1 commenti ricevuti.%1 comments received.PumpController(1 commento ricevuto.1 comment received.PumpController AzioniActionsPumpControllerAttivitActivityPumpController,Aggiungendo oggetti...Adding items...PumpControllerJAggiungendo una persona alla lista...Adding person to list...PumpControllertTutti i dati iniziali ricevuti. Inizializzazione completa.3All initial data received. Initialization complete.PumpControllerLApplicazione autorizzata con successo.$Application authorized successfully.PumpController0Errore di AutorizzazioneAuthorization errorPumpControllerAutorizzato ad utilizzare l'account %1. Ricevendo i dati iniziali.3Authorized to use account %1. Getting initial data.PumpController>Avatar pubblicato con successo.Avatar published successfully.PumpController Avatar caricato.Avatar uploaded.PumpController Richiesta Errata Bad RequestPumpControllerHCommento %1 pubblicato con successo.Comment %1 posted successfully.PumpControllerHCommento %1 aggiornato con successo. Comment %1 updated successfully.PumpController(Creando un gruppo...Creating group...PumpControllerBCreando la lista delle persone...Creating person list...PumpControllerHEliminando la lista delle persone...Deleting person list...PumpControllerDErrore durante la connessione a %1Error connecting to %1PumpControllerPreferiti FavoritesPumpController8File scaricato con successo.File downloaded successfully.PumpControllernFile caricato con successo. Pubblicando il messaggio....File uploaded successfully. Posting message...PumpControllerFollowers FollowersPumpControllerFollowing FollowingPumpController6Segui %1 (%2) con successo.Following %1 (%2) successfully.PumpControllerProibito ForbiddenPumpControllerGateway TimeoutGateway TimeoutPumpController"Ottenendo '%1'...Getting '%1'...PumpController6Ricevendo il token OAuth...Getting OAuth token...PumpControllerFOttenendo la lista della persona...Getting a person list...PumpController.Ricevendo i commenti...Getting comments...PumpController2Ricevendo i "mi piace"...Getting likes...PumpControllerJRicevendo la lista dei 'Followers'...Getting list of 'Followers'...PumpControllerJRicevendo la lista dei 'Following'...Getting list of 'Following'...PumpControllerFRocevendo le liste delle persone...Getting list of person lists...PumpControllerSparitoGonePumpController<Gruppo %1 creato con successo.Group %1 created successfully.PumpController@Unito al gruppo %1 con successo.Group %1 joined successfully.PumpControllerErrore HTTP HTTP errorPumpController2Errore Interno del ServerInternal Server ErrorPumpController*Unendosi al gruppo...Joining group...PumpController,Lasciando il gruppo...Leaving group...PumpControllerFLasciato il gruppo %1 con successo.Left the %1 group successfully.PumpController("Mi piace" ricevuto.Likes received.PumpControllerZLista dei 'followers' ricevuta completamente.(List of 'followers' completely received.PumpControllerZLista dei 'following' ricevuta completamente.(List of 'following' completely received.PumpController:Lista delle 'Liste' ricevuta.List of 'lists' received.PumpControllerNel frattempo MeanwhilePumpControllerMenzioniMentionsPumpControllerFMessaggio cancellato correttamente.Message deleted successfully.PumpControllertMessaggio aggiunto o rimosso dai "Mi piace" correttamente.&Message liked or unliked successfully.PumpControllerMessaggiMessagesPumpController0Spostato PermanentementeMoved PermanentlyPumpController0Spostato TemporaneamenteMoved TemporarilyPumpControllerNon Trovato Not FoundPumpController Non implementatoNot ImplementedPumpControllerpErrore OAuth in fase di autorizzazione dell'appliazione.*OAuth error while authorizing application.PumpController*Errore supporto OAuthOAuth support errorPumpControllerXLista dei 'followers' ricevuta parzialmente.%Partial list of 'followers' received.PumpControllerXLista dei 'following' ricevuta parzialmente.%Partial list of 'following' received.PumpControllerTLista di persone '%1' creata con successo.&Person list '%1' created successfully.PumpControllerRLista di persone eliminata correttamente.!Person list deleted successfully.PumpController4Lista di persone ricevuta.Person list received.PumpControllerJMessaggio %1 pubblicato con successo.Post %1 published successfully.PumpControllerJMessaggio %1 aggiornato con successo.Post %1 updated successfully.PumpControllerDPost di %1 condiviso con successo.Post by %1 shared successfully.PumpController"Profilo ricevuto.Profile received.PumpController&Profilo aggiornato.Profile updated.PumpController Errore QOAuth %1QOAuth error %1PumpControllerPronto.Ready.PumpControllerRicevuto '%1'.Received '%1'.PumpControllerJRimuovendo una persona dalla lista...Removing person from list...PumpControllerNErrori SSL durante la connessione a %1!SSL errors in connection to %1!PumpController0Servizio non DisponibileService UnavailablePumpController~Alcuni dati iniziali non sono stati ricevuti anche dopo diversi tentativi. Qualcosa potrebbe non funzionare sul tuo server. Potresti essere comunque in grado di usare il servizio normalmente.Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally.PumpControllerAlcuni dati iniziali non sono stati ricevuti. Riavvio inizializzazione...@Some initial data was not received. Restarting initialization...PumpControllerbAspettando ancora il tuo profilo. Provo ancora...*Still waiting for profile. Trying again...PumpControllerVHai smesso di seguire %1 (%2) con successo.'Stopped following %1 (%2) successfully.PumpControllerL'applicazione non ancora autorizzata con il tuo server. Registrazione...FThe application is not registered with your server yet. Registering...PumpController@Non ci sono account autorizzati.There is no authorized account.PumpControllerC' stato un errore OAuth nel tentativo di ottenere il token di autorizzazione.EThere was an OAuth error while trying to get the authorization token.PumpControllerTimelineTimelinePumpControllerNon Autorizzato UnauthorizedPumpControllerJCodice di errore HTTP non gestito: %1Unhandled HTTP error code %1PumpControllerdMessaggio senza titolo %1 pubblicato con successo.(Untitled post %1 published successfully.PumpControllerdMessaggio senza titolo %1 aggiornato con successo.&Untitled post %1 updated successfully.PumpControllerCaricando %1 Uploading %1PumpControllerJIn attesa della password del proxy...Waiting for proxy password...PumpControllerProbabilmente hai bisogno di installare il plugin OpenSSL per QCA: %1, %2 o simili.KYou probably need to install the OpenSSL plugin for QCA: %1, %2 or similar.PumpControllerLa tua installazione di QOAuth, libreria usata da Dianara, sembra che non abbia il supporto HMAC-SHA1._Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support.PumpController$%1 post in totale.%1 posts in total.TimeLine'%1' non pu essere aggiornato perch un commento in fase di composizione.E'%1' cannot be updated because a comment is currently being composed.TimeLine.Timeline delle AttivitActivity TimelineTimeLineCompletato il processo, il tuo profilo e le timelines dovrebbero aggiornarsi automaticamente.RAfter the process is done, your profile and timelines should update automatically.TimeLineClicca qui o premi Control+G per saltare ad una pagina specifica8Click here or press Control+G to jump to a specific pageTimeLineFDianara un client <b>Pump.io</b>.#Dianara is a Pump.io client.TimeLineBlog di DianaraDianara's blogTimeLine:Timeline dei messaggi direttiDirect Messages TimelineTimeLine,Timeline dei PreferitiFavorites TimelineTimeLinePrima di tutto, configura il tuo account nel men <b>Configurazione - Account</b>.FFirst, configure your account from the Settings - Account menu.TimeLinezQui potrai vedere messaggi indirizzati specificatamente a te.4Here, you'll see posts specifically directed to you.TimeLinese non hai ancora un account Pump, puoi averne uno al seguente indirizzo, per esempio:]If you don't have a Pump account yet, you can get one at the following address, for instance:TimeLinePi recentiNewerTimeLine UltimiNewestTimeLinePi vecchiOlderTimeLine Pagina %1 di %2.Page %1 of %2.TimeLineRMessaggi e commenti che ti sono piaciuti. Posts and comments you've liked.TimeLinehPremi <b>F1</b> se vuoi aprire la finestra di Aiuto.4Press F1 if you want to open the Help window.TimeLine(Guida Utente Pump.ioPump.io User GuideTimeLine6Mostra %1 posts per pagina.Showing %1 posts per page.TimeLinePrenditi un momento per dare uno sguardo ai men ed alla finestra di Configurazione.DTake a moment to look around the menus and the Configuration window.TimeLineCi sono tooltip ovunque, quindi se ti fermi col mouse sopra ad un bottone o ad un testo, probabilmente vedrai informazioni extra.There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information.TimeLine(Benvenuto in DianaraWelcome to DianaraTimeLinePuoi anche impostare i dettagli e la foto del tuo profilo nel menu <b>Configurazione - Modifica profilo</b>.\You can also set your profile data and picture from the Settings - Edit Profile menu.TimeLine6Qui vedrai i tuoi messaggi.You'll see your own posts here.TimeLine%1 giorni fa %1 days ago Timestamp%1 ore fa %1 hours ago Timestamp%1 minuti fa%1 minutes ago Timestamp%1 mesi fa %1 months ago Timestamp%1 anni fa %1 years ago TimestampUn minuto fa A minute ago TimestampUn mese fa A month ago TimestampUn anno fa A year ago TimestampUn'ora fa An hour ago TimestampProssimamente In the future Timestamp*Timestamp non Valida!Invalid timestamp! Timestamp"In questo istanteJust now TimestampIeri Yesterday Timestamp&Chiudi&Close UserPostsRicevuto '%1'.Received '%1'. UserPostsdianara-v1.3.2/translations/dianara_it.ts000644 000764 000764 00000635511 12614520545 020077 0ustar00janjan000000 000000 ASActivity Public Pubblico %1 by %2 1=kind of object: note, comment, etc; 2=author's name %1 di %2 ASObject Note Noun, an object type Nota Article Noun, an object type Articolo Image Noun, an object type Immagine Audio Noun, an object type Audio Video Noun, an object type Video File Noun, an object type File Comment Noun, as in object type: a comment Commento Group Noun, an object type Gruppo Collection Noun, an object type Collezione Other As in: other type of post Altro No detailed location Nessuna località dettagliata Deleted on %1 Eliminato il %1 and one other e nessun altro and %1 others e %1 altri ASPerson Hometown Città AccountDialog Your Pump.io address: Il tuo indirizzo Pump.io: Webfinger ID, like username@pumpserver.org Webfinger ID, esempio nomeutente@pumpserver.org Your address, as username@server Il tuo indirizzo, come nomeutente@server Get &Verifier Code Ottieni il Codice di &Verifica Verifier code: Codice di Verifica: Enter or paste the verifier code provided by your Pump server here Inserisci o incolla il codice di verifica fornito dal tuo server Pump qui &Save Details &Salva i Dati If the browser doesn't open automatically, copy this address manually Se il browser non si apre automaticamente, copia questo indirizzo manualmente Account Configuration Configurazione Account First, enter your Webfinger ID, your pump.io address. Prima inserisci il tuo Webfinger ID, il tuo indirizzo Pump.io. Your address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. Il tuo indirizzo assomiglia a nomeutente@pumpserver.org, e lo puoi trovare sul tuo profilo, nell'interfaccia web. If your profile is at https://pump.example/yourname, then your address is yourname@pump.example Se il tuo profilo, per esempio, è https://pumpserver.org/nomeutente, di conseguenza il tuo indirizzo è nomeutente@pumpserver.org If you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. 1=link to website Se non hai ancora un account, puoi iscriverti su %1. Questo link ti porterà su un server pubblico casuale. If you need help: %1 Se hai bisogno di aiuto: %1 Pump.io User Guide Guida Utente Pump.io Your address, like username@pumpserver.org Il tuo indirizzo, tipo nomeutente@serverpump.org After clicking this button, a web browser will open, requesting authorization for Dianara Dopo aver cliccato questo bottone, il browser web si dovrebbe aprire, richiedendo l'autorizzazione per Dianara Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. Don't translate the VERIFIER word! Una volta autorizzato Dianara dall'interfaccia web del tuo server Pump.io, riceverai un codice chiamato VERIFIER. Copialo e incollalo nel campo qui sotto. Enter the verifier code provided by your Pump server here Inserisci il codice di verifica ottenuto dal tuo server Pump.io qui Paste the verifier here Incolla il codice di verifica qui &Authorize Application &Autorizza Applicazione &Cancel &Cancella Your account is properly configured. Il tuo account è configurato correttamente. Press Unlock if you wish to configure a different account. Premi Sblocca se vuoi configurare un altro account. &Unlock &Sblocca A web browser will start now, where you can get the verifier code Ora si avviera il browser web, dove potrai ottenere il codice di verifica Your Pump address is invalid Il tuo indirizzo Pump.io non è valido Verifier code is empty Il campo del codice di verifica è vuoto Dianara is authorized to access your data Dianara è autorizzato ad acceder ai tuoi dati AudienceSelector 'To' List Lista 'A' 'CC' List Lista 'CC' 'Cc' List Lista 'Cc' &Add to Selected &Aggiungi ai Selezionati All Contacts Tutti i Contatti Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. Seleziona le persone dalla lista a sinistra. Puoi trascinarle con il mouse, click o doppio click su di esse, o selezionarle e usare il bottone qui sotto. Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. ON THE LEFT should change to ON THE RIGHT in RTL languages Seleziona le persone dalla lista a sinistra. Puoi trascinarle con il mouse, click o doppio click su di esse, o selezionarle e usare il bottone qui sotto. Clear &List Pulisci la &Lista &Done &Fatto &Cancel &Cancella Selected People Persone Selezionate AvatarButton Open %1's profile in web browser Apri il profilo di %1 nel browser web Open your profile in web browser Apri il tuo profilo nel browser web Send message to %1 Invia messaggio a %1 Browse messages Stop following Smetti di seguire Follow Segui Stop following? Smettere di seguire? Are you sure you want to stop following %1? Sei sicuro di voler smettere di seguire %1? &Yes, stop following &Si, smetti di seguire &No &No ColorPicker Change Cambia Choose a color Comment Posted on %1 Pubblicato il %1 Modified on %1 Modificato il %1 Like or unlike this comment Decidi se ti piace o non ti piace questo commento Quote This is a verb, infinitive Quotare Reply quoting this comment Rispondi quotando questo commento Edit Modifica Modify this comment Modifica questo commento Delete Elimina Erase this comment Elimina questo commento Unlike Non mi piace Like Mi piace %1 like this comment Plural: %1=list of people like John, Jane, Smith A %1 piace questo commento %1 likes this comment Singular: %1=name of just 1 person A %1 piace questo commento WARNING: Delete comment? ATTENZIONE: Cancellare il commento? Are you sure you want to delete this comment? Sei sicuro di voler cancellare questo commento? &Yes, delete it &Si, cancellalo &No &No CommenterBlock You can press Control+Enter to send the comment with the keyboard Premi Control+Invio per inviare il commento con la tastiera &Cancel &Annulla Reload comments Ricarica commenti Comment Infinitive verb Commentare Cancel Cancella Press ESC to cancel the comment if there is no text Premi ESC per annullare il commento, se non c'è testo Check for comments Show all %1 comments Mostra tutti i %1 commenti Comments are not available Error: Already composing Errore: stai già scrivendo You can't edit a comment at this time, because another comment is already being composed. Non puoi modificare ora il commento, perchè ne stai già scrivendo un altro. Editing comment Modificando il commento Posting comment failed. Try again. Invio del commento fallito. Prova di nuovo. Sending comment... Inviando il commento... Updating comment... Aggiornando il commento... Comment is empty. Il commento è vuoto. Composer Type a message here to post it Scrivi qui un messaggio per pubblicarlo Click here or press Control+N to post a note... Clicca qui o premi Control+N per pubblicare una nota... Symbols Simboli Formatting Formattazione Normal Normale Bold Grassetto Italic Corsivo Underline Sottolineato Strikethrough Barrato Header Titolo List Lista Table Tabella Preformatted block Blocco preformattato Quote block Blocco citazioni Make a link Crea un link Insert an image from a web site Inserisci un'immagine da un sito web Insert line Inserisci una linea Insert as image? Inserire come immagine? The link you are pasting seems to point to an image. Il collegamento che stai incollando sembra puntare ad un'immagine. Insert as visible image Inserisci come immagine visibile Insert as link Inserisci come collegamento Table Size Dimensioni Tabella How many rows (height)? Quante righe (altezza)? How many columns (width)? Quante colonne (larghezza)? Type or paste a web address here. You could also select some text first, to turn it into a link. Scrivi o incolla un indirizzo web qui. Puoi anche selezionare del testo, per convertirlo poi in un link. Type or paste the image address here. The link must point to the image file directly. Scrivi o incolla l'indirizzo dell'immagine qui. Il link deve puntare direttamente all'immagine. &Format Button for text formatting and related options &Formato Text Formatting Options Opzioni di formattazione del testo Paste Text Without Formatting Incolla testo senza formattazione Type a comment here Scrivi un commento qui Insert a link Inserisci un link Make a link from selected text Crea un link con il testo selezionato Type or paste a web address here. The selected text (%1) will be converted to a link. Digita o incolla un indirizzo web qui. Il testo selezionato (%1) sarà convertito in un link. Insert an image from a URL Inserisci un'immagine da un URL Error: Invalid URL Errore: URL non valido The address you entered (%1) is not valid. Image addresses should begin with http:// or https:// L'indirizzo inserito (%1) non è valido. Gli indirizzi di un'immagine dovrebbero iniziare con http:// o https:// Cancel message? Cancellare il messaggio? Are you sure you want to cancel this message? Sei sicuro di voler cancellare questo messaggio? &Yes, cancel it &Si, cancellalo &No &No ConfigDialog minutes minuti Top In alto Bottom In basso Left side A sinistra Right side A destra Program Configuration Configurazione del programma Timeline &update interval Intervallo di &aggiornamento della timeline &Tabs position Posizione delle &tab &Movable tabs &Tab mobili Minor Feeds Feed Minori posts Goes after a number, as: 25 posts elementi &Posts per page, main timeline &Elementi per pagina, timeline principale posts This goes after a number, like: 10 posts elementi Posts per page, &other timelines Elementi per pagina, &altre timeline Show frame of deleted posts UNDECIDED string; FIXME Mostra il box dei post cancellati Ignore SSL errors in images Public posts as &default Messaggi pubblici come &default Pro&xy Settings Impostazioni del Pro&xy Network configuration Configurazione di Rete Set Up F&ilters Imposta F&iltri Filtering rules Regole di filtraggio Highlighted activities, except mine Attività selezionate, escluse le mie Any highlighted activity Qualsiasi attività selezionata Always Sempre Never Mai Show snippets in minor feed Mostra snippets nella timeline laterale characters This is a suffix, after a number caratteri Snippet limit Limite Snippet Post Titles Titoli dei Post Post Contents Contenuto dei post Comments Commenti Minor Feed Feed laterale You are among the recipients of the activity, such as a comment addressed to you. Sei tra i destinatari dell'attività, per esempio un commento indirizzato a te. Used also when highlighting posts addressed to you in the timelines. Usato anche quando i post in evidenza sono indirizzati a te nelle timeline. The activity is in reply to something done by you, such as a comment posted in reply to one of your notes. L'attività è in risposta a qualcosa fatto da te, come un commento postato in risposta a una delle tue note. You are the object of the activity, such as someone adding you to a list. Sei l'oggetto dell'attività, per esempio qualcuno ti ha aggiunto ad una lista. The activity is related to one of your objects, such as someone liking one of your posts. L'attività è legata a uno dei tuoi oggetti, per esempio a qualcuno piace un tuo post. Used also when highlighting your own posts in the timelines. Usato anche per evidenziare i tuoi post nelle timeline. Item highlighted due to filtering rules. Oggetto evidenziato in base alle regole dei filtri. Item is new. Nuovo Elemento. Show snippets in minor feeds Mostra snippets nei feed minori Show deleted posts Mostra post eliminati Hide duplicated posts Nascondi post duplicati Avatar size Dimensione avatar Show extended share information Mostra informazioni di condivisione estese Show extra information Mostra informazioni extra Highlight post author's comments Evidenzia i commenti dell'autore del post Highlight your own comments Evidenza i tuoi commenti Use media filename as initial post title Usa il nome del media come titolo iniziale del post Show character counter Mostra contatore di caratteri Don't inform followers when following someone Don't inform followers when handling lists As system notifications Come notifiche di sistema Using own notifications Usando proprie notifiche Don't show notifications Non mostrare notifiche Notification Style Stile di notifica Notify when receiving: Notifica quando ricevi: New posts Nuovi post Highlighted posts Post in evidenza New activities in minor feed Nuove attività nel feed secondario Highlighted activities in minor feed Attività in evidenza nel feed secondario Default Default System iconset, if available Icone di sistema, se disponibili Show your current avatar Mostra il tuo avatar attuale Custom icon Icona personalizzata System Tray Icon &Type &Tipo di Icona di Sistema Hide window on startup General Options Opzioni Generali Fonts Font Colors Colori Timelines Timeline Posts Post Composer Componi Privacy Notifications Notifiche System Tray Icone di Sistema S&elect... S&eleziona... Custom &Icon &Icona Personalizzata Select custom icon Seleziona un'icona personalizzata Dianara stores data in this folder: Dianara salva i dati in questa cartella: Left side tabs on left side/west; RTL not affected A sinistra Right side tabs on right side/east; RTL not affected A destra Show information for deleted posts Jump to new posts line on update Only for images inserted from web sites. Use with care. Use attachment filename as initial post title Usa il nome del file dell'allegato come titolo iniziale del post Inform only the author when liking things &Save Configuration &Salva Configurazione &Cancel &Cancella This is a system notification System notifications are not available! Own notifications will be used. This is a basic notification Image files File immagine All files Tutti i files Invalid image Immagine non valida The selected image is not valid. L'immagine selezionata non è valida. ContactCard Hometown Città Joined: %1 Membro dal: %1 Updated: %1 Aggiornato: %1 Bio for %1 Abbreviation for Biography, but you can use the full word; %1=contact name Bio di %1 This user doesn't have a biography Questo utente non ha una biografia No biography for %1 %1=contact name Nessuna biografia per %1 Open Profile in Web Browser Apri il profilo nel browser web Send Message Invia Messaggio Browse Messages In Lists... Nelle liste... User Options Opzioni utente Follow Segui Stop Following Smetti di seguire Stop following? Smettere di seguire? Are you sure you want to stop following %1? Sei sicuro di voler smettere di seguire %1? &Yes, stop following &Si, smetti di seguire &No &No ContactList Type a partial name or ID to find a contact... Scrivi parzialmente un nome o l'ID per trovare un contatto... F&ull List Lista Com&pleta ContactManager username@server.org or https://server.org/username nomeutente@pumpserver.org o https://pumpserver.org/nomeutente &Enter address to follow: &Inserisci l'indirizzo da seguire: &Follow &Segui Reload Followers Ricarica Followers Reload Following Ricarica Following Export Followers Esporta Followers Export Following Esporta Following Reload Lists Ricarica Liste &Neighbors Follo&wers Follo&wers Followin&g Foll&owing &Lists &Liste Export list of 'following' to a file Esporta la lista dei 'Following' in un file Export list of 'followers' to a file Esporta la lista dei 'Followers' in un file DownloadWidget Download Scarica Save the attached file to your folders Salva il file allegato nelle tue cartelle Cancel Annulla Save File As... Salva File Come... All files Tutti i files Abort download? Fermare download? Do you want to stop downloading the attached file? Vuoi fermare il download del file allegato? &Yes, stop &Si, ferma &No, continue &No, continua Download aborted Download fermato Download completed Download completato Download failed Download fallito Downloading %1 KiB... Scaricando %1 KiB... %1 KiB downloaded %1 KiB scaricati EmailChanger Change E-mail Address Change Cambia &Cancel &Cancella E-mail Address: Again: Your Password: E-mail addresses don't match! Password is empty! FilterEditor Filter Editor Modifica Filtri Application Applicazione %1 if %2 contains: %3 This explains a filter rule, like: Hide if Author ID contains JohnDoe %1 se %2 contiene: %3 Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. Qui puoi impostare regole per nascondere o mettere in evidenza le attività. Puoi filtrare per contenuto, autore o applicazione. Per esempio, puoi filtrare messaggi postati dall'applicazione Open Farm Game, o quelli che contengono la parola NSFW nel messaggio. Puoi anche evidenziare i messaggi che contengono il tuo nome. Hide Nascondi Highlight Evidenzia Post Contents Contenuto del post Author ID ID dell'autore Activity Description Descrizione dell'attività Keywords... Parole chiave... &Add Filter &Aggiungi Filtro Filters in use Filtri in uso &Remove Selected Filter &Rimuovi i Filtri Selezionati &Save Filters &Salva Filtri &Cancel &Annulla if se contains contiene &New Filter &Nuovo Filtro C&urrent Filters Filt&ri Salvati FirstRunWizard Welcome Wizard Welcome to Dianara! This wizard will help you get started. You can access this window again at any time from the Help menu. The first step is setting up your account, by using the following button: Configure your &account Once you have configured your account, it's recommended that you edit your profile and add an avatar and some other information, if you haven't done so already. &Edit your profile By default, Dianara will post only to your followers, but it's recommended that you post to Public, at least sometimes. Post to &Public by default Open general program &help window &Show this again next time Dianara starts &Close &Chiudi FontPicker Change Cambia Choose a font HelpWidget Basic Help Aiuto di Base Getting started Per iniziare The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. La prima volta che fai partire Dianara, dovresti vedere la finestra di Configurazione dell'Account. Inserisci il tuo indirizzo Pump.io come nome@server e premi Ottieni Codice di Verifica. Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. Poi, il tuo browser predefinito dovrebbe caricare la pagina di autorizzazione nel tuo server Pump.io. Qui, dovrai copiare il VERIFIER code, e incollarlo nel secondo campo di Dianara. Quindi premi Autorizza Applicazione e, una volta confermato, premi Salva Dettagli. At this point, your profile, contact lists and timelines will be loaded. A questo punto il tuo profilo, le liste contatti e le timeline saranno caricate. You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. Dovresti dare uno sguardo alla finestra di Configurazione del Programma, sotto Impostazioni - Configura Dianara. Ci sono diverse opzioni interessanti là. Settings Impostazioni You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. Puoi configurare diverse cose secondo le tue preferenze, come il tempo di aggiornamento delle timeline, quanti post per pagina vuoi vedere, colori di evidenziazione, notifiche o come visualizzare l'icona di sistema. Timelines Timeline Contents Contenuti Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. Tieni presente che ci sono molti posti in Dianara dove puoi ottenere più informazioni fermandoti sopra alcuni testi o bottoni col mouse, e aspettare che il suggerimento appaia. Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. Qui puoi anche attivare l'opzione di pubblicare sempre i tuoi post pubblici per default. Puoi sempre cambiare questa preferenza al momento di postare. The main timeline, where you'll see all the stuff posted or shared by the people you follow. La timeline principale, dove puoi vedere tutti gli elementi postati o condivisi da persone che segui. Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. La timeline dei messaggi, dove vedrai messaggi inviati a te specificatamente. Questi messaggi potrebbero essere stati inviati anche ad altre persone. Activity timeline, where you'll see your own posts, or posts shared by you. La timeli delle attività, dove vedrai i tuoi post e i post che hai condiviso. Favorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. La timeline dei preferiti, dove vedrai i post e i commenti che ti piacciono. Può essere usata come sistema di bookmark. The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. La quinta timeline è la timeline minore, conosciuta anche come Meanwhile (nel frattempo). Questa è visibile a sinistra, ma può anche essere nascosta. Qui vedrai attività di tutte le persone che segui, come commenti, mi piace e segui persone. These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. Queste attività potrebbero avere un '+' vicino. Premilo per aprire il post al quale sono riferite. Anche qui potrai avere ulteriori informazioni se rimani fermo col mouse sul pulsante. Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. Dianara offers a D-Bus interface that allows some control from other applications. The interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. Posting Postare If you're new to Pump.io, take a look at this guide: Se sei nuovo su Pump.io, dai uno sguardo a questa guida: New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. Nuovi messaggi appaiono evidenziati in un colore diffeerente. Puoi segnarli come già letti cliccando in una parte vuota del messaggio. You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. Puoi postare note cliccando il campo di testo in alto oppure premendo Control+N. Impostare un titolo per il post è opzionale, ma caldamente consigliato, perchè aiuta gli altri a identificarlo nella timeline laterale, nelle notifiche mail, ecc. It is possible to attach images, audio, video, and general files, like PDF documents, to your post. Allegare immagini, video o audio ed altri file come PDF è ora possibile. You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. Puoi usare il pulsante Formattazione per formattare il tuo testo, per esempio renderlo grassetto o corsivo. Alcune di queste opzioni richiedono che del testo sia selezionato prima che siano usate. You can select who will see your post by using the To and CC buttons. Puoi selezionare chi vedrà il tuo post usando i pulsanti A e CC. If you add a specific person to the 'To' list, they will receive your message in their direct messages tab. Se aggiungi specifiche persone alla lista A, riceveranno una notifica del tuo messaggio nella tab dei messaggi diretti. You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. Puoi creare messaggi privati aggiungendo specifiche persone a queste liste e rimuovendo Followers e Pubblico. Managing contacts Gestire i contatti You can see the lists of people you follow, and who follow you from the Contacts tab. Puoi vedere le liste di persone che segui e che ti seguono dalla tab Contatti. There, you can also manage person lists, used mainly to send posts to specific groups of people. Qui puoi anche gestire liste di persone, usate principalmente per inviare post a specifici gruppi. You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. Puoi cliccare su qualsiasi avatar nei post, nei commenti, e nella timeline laterale e comparirà un menù con diverse opzioni, una di queste serve per seguire o smettere di seguire quella persona. Keyboard controls Controlli da Tastiera Pump.io User Guide Guida Utente Pump.io There are seven timelines: Ci sono sette timeline: The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). La sesta e la settima timeline sono timeline minori o secondarie, simili a "Nel Frattempo", ma contiene solo attività indirizzate direttamente a te (Menzioni) e attività create da te (Azioni). You can select who will see your post by using the To and Cc buttons. Puoi selezionare chi vedrà il tuo post usando i pulsanti A e Cc. You can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. Puoi anche digitare '@' e il primo carattere del nome di un contatto per far apparire un popup con le scelte corrispondenti. Choose one with the arrows and press Enter to complete the name. This will add that person to the recipients list. Scegline uno con le frecce e premi Invio per completare il nome. Questo aggiungerà la persona alla lista dei destinatari. You can also send a direct message (initially private) to that contact from this menu. Puoi anche inviare un messaggio diretto (inizialmente privato) al contatto da questo menu. You can find a list with some Pump.io users and other information here: Puoi trovare una lista con alcuni utenti Pump.io ed altre informazioni qui: Users by language The most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. Le azioni più comuni che si trovano nei menù hanno scorciatorie da tastiera scritte vicino a loro, come F5 o Control+N. Besides that, you can use: Oltre a quello, puoi usare: Control+Up/Down/PgUp/PgDown/Home/End to move around the timeline. Control+Su/Giù/PgSu/PgGiù/Home/Fine per muoverti nelle timeline. Control+Left/Right to jump one page in the timeline. Control+Sinistra/Destra per saltare da una pagina all'altra nella timeline. Control+G to go to any page in the timeline directly. Control+G va in qualsiasi pagina della timeline direttamente. Control+1/2/3 to switch between the minor feeds. Control+1/2/3 per spostarti tra i feed minori. Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. Control+Invio per inviare il post quando hai terminato di comporre la nota o il commento. Se la nota è vuota, puoi cancellarla premendo ESC. Command line options Opzioni da riga di comando The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. LEFT SIDE should change to RIGHT SIDE on RTL languages La quinta timeline è la timeline minore, conosciuta anche come Meanwhile (nel frattempo). Questa è visibile a sinistra, ma può anche essere nascosta. Qui vedrai attività di tutte le persone che segui, come commenti, mi piace e segui persone. Choose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. Scegline uno con i tasti freccia e premi Invio per completare un nome. Questo aggiungerà quella persona alla lista di destinatari. There is a text field at the top, where you can directly enter addresses of new contacts to follow them. While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. Mentre componi una nota, premi Invio per saltare dal titolo al messaggio. Premendo la freccia Su mentre sei all'inizio del messaggio, torni al titolo. Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. Control+Invio per terminare la creazione della lista di destinatari per il post, per le liste 'A' o 'Cc'. You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. Puoi usare il parametro --config per avviare il programma con una configurazione differente. Questo può essere utile per usare due o più account. Puoi anche avviare due istanze di Dianara contemporaneamente. Use the --debug parameter to have extra information in your terminal window, about what the program is doing. Usa il parametro --debug per avere informazioni extra nel tuo terminale, su quello che il programma sta facendo. If your server does not support HTTPS, you can use the --nohttps parameter. If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. &Close &Chiudi ImageViewer Image Immagine Resolution and file size Risoluzione e dimensione del file ESC to close, secondary-click for options ESC per chiudere, click destro per opzioni &Save As... &Salva come... &Restart Animation &Replay Animazione &Close &Chiudi Save Image... Salvataggio Immagine... Close Viewer Chiudi Visualizzatore Save Image As... Salva Immagine Come... Image files Files Immagine All files Tutti i Files Error saving image Errore durante il salvataggio dell'immagine There was a problem while saving %1. Filename should end in .jpg or .png extensions. C'è stato un problema salvando %1. L'estensione del file dovrebbe essere .jpg o .png. ListsManager Name Nome Members Membri Add Mem&ber Aggiungi Mem&bro &Remove Member &Rimuovi Membro &Delete Selected List &Cancella la Lista Selezionata Add New &List Aggiungi Nuova &Lista Create L&ist Crea L&ista &Add to List &Aggiungi alla Lista Are you sure you want to delete %1? 1=Name of a person list Sei sicuro di voler cancellare %1? Remove person from list? Rimuovere la persona dalla lista? Are you sure you want to remove %1 from the %2 list? 1=Name of a person, 2=name of a list Sei sicuro di voler rimuovere %1 dalla lista %2? &Yes &Si Type a name for the new list... Digita un nome per la nuova lista... Type an optional description here Digita una descrizione opzionale qui WARNING: Delete list? ATTENZIONE: Cancellare la lista? &Yes, delete it &Si, cancellala &No &No LogViewer Log Log Clear &Log Pulisci &Log &Close &Chiudi MainWindow Meanwhile... Nel frattempo... Side &Panel Pannello &laterale Status &Bar &Barra di stato Total posts: %1 Post totali: %1 &Timeline T&imeline The main timeline Timeline principale &Activity &Attività Your own posts I tuoi messaggi Your favorited posts I tuoi messaggi preferiti &Messages &Messaggi Messages sent explicitly to you Messaggi inviati esplicitamente a te &Contacts &Contatti Initializing... Inizializzando... Your account is not configured yet. Il tuo account non è ancora stato configurato. Dianara started. Dianara è partito. Actions Azioni The people you follow, the ones who follow you, and your person lists Le persone che segui, quelle che ti seguono, e le tue liste di persone Running with Qt v%1. In funzione con Qt v%1. &Session &Sessione Update Minor &Feed Aggiorna la timeline late&rale Update Mentions Aggiorna Menzioni Update Actions Aggiorna Azioni Auto-update &Timelines Auto-aggiorna &Timeline Mark All as Read Segna tutti come letti &Post a Note &Pubblica una nota &Quit &E&sci &View &Visualizzazione Full &Screen Sc&hermo Intero &Log &Log S&ettings Configura&zione Edit &Profile Modifica &profilo &Account A&ccount Basic &Help Ai&uto di Base Report a &Bug Riporta un &Bug Pump.io User &Guide &Guida Utente Pump.io List of Some Pump.io &Users Elenco di alcuni &Utenti Pump.io Pump.io &Network Status Website Auto-updating enabled Auto-aggiornamenti abilitati Auto-updating disabled Autp-aggiornamenti disabilitati Proxy password required Password Proxy Richiesta You have configured a proxy server with authentication, but the password is not set. Hai configurato un server proxy con autenticazione, ma la password non è impostata. Enter the password for your proxy server: Inserisci la password del tuo server proxy: Your biography is empty La tua biografia è vuota Click to edit your profile Clicca per modificare il tuo profilo Starting automatic update of timelines, once every %1 minutes. Attiva l'aggiornamento automatico delle timeline, ogni %1 minuti. Stopping automatic update of timelines. Ferma l'aggiornamento automatico delle timeline. Received %1 older posts in '%2'. %1 is a number, %2 = name of a timeline 1 more pending to receive. singular, one post 1 in attesa di ricezione. %1 more pending to receive. plural, several posts %1 in attesa di ricezione. Also: Last update: %1 Ultimo aggiornamento: %1 '%1' updated. %1 is the name of a feed '%1' aggiornato. 1 pending to receive. singular, one post 1 in attesa di ricezione. %1 pending to receive. plural, several posts %1 in attesa di ricezione. 1 highlighted. singular, refers to a post 1 evidenziato. %1 highlighted. plural, refers to posts %1 evidenziati. Received %1 older activities in '%2'. %1 is a number, %2 = name of feed 1 more pending to receive. singular, 1 activity 1 in attesa di ricezione. %1 more pending to receive. plural, several activities %1 in attesa di ricezione. Shutting down Dianara... System tray icon is not available. L'icona di sistema non è disponibile. Dianara cannot be hidden in the system tray. Dianara non può essere nascosto nella barra delle notifiche di sistema. Do you want to close the program completely? Vuoi chiudere completamente il programma? Timeline updated at %1. Timeline aggiornata alle %1. Press F1 for help Premi F1 per aiuto Click here to configure your account Update %1 Aggiorna %1 No new posts. Nessun nuovo post. Your Pump.io account is not configured Il tuo account Pump.io non è configurato Mentions Menzioni There is 1 new activity. C'è una nuova attività. There are %1 new activities. Ci sono %1 nuove attività. 1 pending to receive. singular, 1 activity 1 in attesa di ricezione. %1 pending to receive. plural, several activities %1 in attesa di ricezione. 1 highlighted. singular, refers to an activity 1 evidenziato. %1 highlighted. plural, refers to activities %1 evidenziati. 1 filtered out. singular, refers to one activity 1 filtrato. %1 filtered out. plural, several activities %1 filtrati. No new activities. Nessuna nuova attività. Error storing image! %1 bytes Link to: %1 Link a: %1 Marking everything as read... Dianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) Dianara è distribuita su licenza GNU GPL, ed utilizza alcune icone Oxygen: http://www.oxygen-icons.org/ (licenza LGPL) Closing due to environment shutting down... Quit? Chiudere? You are composing a note or a comment. Stai componendo una nota o un commento. Do you really want to close Dianara? Vuoi veramente chiudere Dianara? &Yes, close the program &Si, chiudi il programma &No &No &Configure Dianara Configura &Dianara Minor activities done by everyone, such as replying to posts Attività minori fatte da chiunque, come rispondere ai post Minor activities addressed to you Attività minori indirizzate a te Minor activities done by you Attività minori create da te &Update Timeline Aggiorna Timeli&ne Update &Messages Aggiorna &Messaggi Update &Activity Aggiorna &Attività Update Fa&vorites Aggiorna &Preferiti &Toolbar Barra degli Strumen&ti &Filters and Highlighting &Filtri e Evidenziazione &Help Ai&uto Show Welcome Wizard Visit &Website Visita il sito &web Some Pump.io &Tips Alcuni consigli per &Pump.io About &Dianara Informazioni su &Dianara Toolbar Barra degli Strumenti Open the log viewer Apri il visualizzatore del log 1 filtered out. singular, refers to a post 1 filtrato. %1 filtered out. plural, refers to posts %1 filtrati. 1 deleted. singular, refers to a post 1 cancellato. %1 deleted. plural, refers to posts %1 cancellati. Favor&ites Prefer&iti Minor feed updated at %1. Timeline laterale aggiornata %1. Minor feed updated. Timeline laterale aggiornata. With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. Con Dianara puoi vedere le tue timeline, creare nuovi post, caricare immagini e altri media, interagire con post, organizzare i tuoi contatti e seguire nuove persone. English translation by JanKusanagi. TRANSLATORS: Change this with your language and name. If there was another translator before you, add your name after theirs ;) Traduzione italiana a cura di Howcanuhavemyusername (or Metal Biker) (howcanuhavemyusername@microca.st). &Hide Window &Nascondi Finestra &Show Window &Mostra la finestra There is 1 new post. C'è 1 nuovo messaggio. There are %1 new posts. Ci sono %1 nuovi messaggi. Timeline updated. Timeline aggiornata. About Dianara Informazioni su Dianara Dianara is a pump.io social networking client. Dianara è un client per il social network pump.io. Thanks to all the testers, translators and packagers, who help make Dianara better! Grazie a tutti i tester, i traduttori e i manutentori dei pacchetti, che hanno aiutato Dianara a migliorare! MinorFeed Older Activities Attività più vecchie Get previous minor activities Ottieni attività precedenti There are no activities to show yet. Non ci sono ancora attività da mostrare. Get %1 newer As in: Get 3 newer (activities) Scarica %1 nuove MinorFeedItem Using %1 Application used to generate this activity Via %1 To: %1 1=people to whom this activity was sent A: %1 Cc: %1 1=people to whom this activity was sent as CC Cc: %1 CC: %1 1=people to whom this activity was sent as CC CC: %1 Open referenced post Apri l'elemento relativo MiscHelpers bytes bytes PageSelector Jump to page Salta alla pagina Page number: Pagina numero: &First As in: first page &Last As in: last page Newer As in: newer pages Più recenti Older As in: older pages Più vecchi Newer Più recenti Older Più vecchi &Go &Vai Go to &last page Vai all'u&ltima pagina &Cancel &Cancella PeopleWidget &Search: &Cerca: Enter a name here to search for it Inserisci qui un nome per cercarlo Add a contact to a list Aggiungi un contatto ad una lista &Cancel &Cancella Post Like Mi piace Like this post Dì che ti piace questo messaggio Click to download the attachment Clicca pe rscaricare l'allegato Post Noun, not verb Pubblica Using %1 1=Program used for posting or sharing Via %1 &Close &Chiudi Type As in: type of object Tipo Modified on %1 Modificato il %1 Parent As in 'Open the parent post'. Try to use the shortest word! Padre Open the parent post, to which this one replies Apri il post padre, quello al quale questo post risponde Modify this post Modifica questo post Join Group Unisciti al Gruppo %1 members in the group %1 membri nel gruppo Image is animated. Click on it to play. L'immagine è animata. Clicca per riprodurre. Loading image... Caricando l'immagine... Attached file File allegato 1 like 1 mi piace 1 comment 1 commento Shared %1 times Condiviso %1 volte Shared on %1 Condiviso su %1 Edited: %1 Modificato: %1 In In Share Condividi Edit Modifica %1 likes %1 mi piace %1 comments %1 commenti Delete Elimina Via %1 Via %1 Posted on %1 1=Date Pubblicato il %1 To A CC CC If you select some text, it will be quoted. Se selezioni del testo, verrà quotato. Unshare Rimuovi condivisione Unshare this post Rimuovi la condivisione di questo elemento Open post in web browser Apri il messaggio nel browser web Cc Cc Copy post link to clipboard Copia il link al messaggio negli appunti Normalize text colors Normalizza i colori del testo Comment verb, for the comment button Commenta Reply to this post. Rispondi a questo post. Share this post with your contacts Condividi questo post con i tuoi contatti Erase this post Cancella questo post Couldn't load image! Attached Audio Audio allegato Attached Video Video allegato Attached File File allegato %1 likes this One person A %1 piace questo elemento %1 like this More than one person A %1 piace questo elemento %1 shared this %1 = One person name %1 ha condiviso questo elemento %1 shared this %1 = Names for more than one person %1 hanno condiviso questo elemento Shared once Condiviso una volta You like this Ti piace Unlike Non mi piace più Share post? Condividi il messaggio? Do you want to share %1's post? Vuoi condividere il messaggio di %1? &Yes, share it &Si, condividilo &No &No Unshare post? Rimuovere la condivisione di questo elemento? Do you want to unshare %1's post? Vuoi rimuovere la condivisione dell'elemento di %1? &Yes, unshare it &Si, non condividerlo WARNING: Delete post? ATTENZIONE: Eliminare il messaggio? Are you sure you want to delete this post? Sei sicuro di voler eliminare questo messaggio? &Yes, delete it &Si, eliminalo Click the image to see it in full size Clicca l'immagine per vederla nelle dimensioni originali ProfileEditor Profile Editor Editor del profilo This is your Pump address Questo è il tuo indirizzo Pump.io This is the e-mail address associated with your account, for things such as notifications and password recovery Questa è l'indirizzo e-mail associato al tuo accout, per inviare notifiche e recuperare la password Change &E-mail... Cambia &e-mail... Change &Avatar... Cambia &avatar... This is your visible name Questo è il tuo nome visibile &Save Profile &Salva profilo &Cancel &Cancella Webfinger ID Webfinger ID E-mail E-mail Avatar Avatar Full &Name &Nome Completo &Hometown Ci&ttà &Bio &Bio Not set In reference to the e-mail not being set for the account Non impostata Select avatar image Scegli l'immagine per l'avatar Image files Files immagine All files Tutti i files Invalid image Immagine non valida The selected image is not valid. L'immagine selezionata non è valida. ProxyDialog Proxy Configuration Configurazione Proxy Do not use a proxy Non usare un proxy Your proxy username Il tuo username del proxy Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. Nota: la password non è salvata in modo sicuro. Se lo desideri, puoi lasciare questo campo vuoto e ti verrà chiesta la password all'avvio. &Save &Salva &Cancel &Annulla Proxy &Type &Tipo di Proxy &Hostname &Hostname &Port &Porta Use &Authentication Usa Autenti&cazione &User &Utente Pass&word Pass&word Publisher Title Titolo Add a brief title for the post here (recommended) (Jan) This one should be updated with the "here" Aggiungi un breve titolo per il post (raccomandato) Picture Immagine Audio Audio Video Video Ad&d... Aggiun&gi... Upload media, like pictures or videos Carica media, come immagini o video Select Picture... Selezionare l'immagine... Find the picture in your folders Trova l'immagine nelle tue cartelle Public Pubblico Followers Followers Lists Liste To... A... CC... CC... Select who will get a copy of this post Scegli chi riceverà una copia di questo messaggio Other as in other kinds of files Altro Cancel Cancella Cancel the post Cancella il messaggio Picture not set Immagine non selezionata Select Audio File... Seleziona File Audio... Find the audio file in your folders Cerca il file audio nelle tue cartelle Audio file not set Il file audio non è impostato Select Video... Seleziona Video... Find the video in your folders Cerca il file video nelle tue cartelle Video not set Video non impostato Select File... Seleziona File... Find the file in your folders Trova il file nelle tue cartelle File not set File non impostato Error: Already composing Errore: stai già scrivendo You can't edit a post at this time, because a post is already being composed. Non puoi modificare il messaggio ora, perchè stai già scrivendo un messaggio. Update Aggiorna Editing post Modifica il messaggio You can't create a message for %1 at this time, because a post is already being composed. Non puoi creare un messaggio per %1 al momento, perche un post è già in fase di composizione. Posting failed. Try again. Invio fallito. Prova di nuovo. Warning: You have no followers yet Avviso: non hai ancora followers You're trying to post to your followers only, but you don't have any followers yet. Stai cercando di postare solo per i tuoi followers, ma non ne hai ancora. If you post like this, no one will be able to see your message. Se posti con queste impostazioni, nessuno sarà in grado di leggere il tuo messaggio. Do you want to make the post public instead of followers-only? Vuoi rendere il post pubblico invece che renderlo visibile solo ai tuoi followers? &Yes, make it public &Si, rendilo pubblico &Cancel, go back to the post &Annulla e torna al post Updating... Aggiornando... Post is empty. Il messaggio è vuoto. File not selected. File non selezionato. Select one image Scegli un'immagine Image files Files immagine Select one file Seleziona un file Invalid file File non valido The file type cannot be detected. Il tipo di file non può essere identificato. All files Tutti i files Since you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. Visto che stai caricando un'immagine, potresti ridurre la sua risoluzione o salvarla in un formato più compresso, come JPG. File is too big File troppo grande Dianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. Dianara limita la dimensione dei file caricati a 10MiB per post, in modo da prevenire problemi di spazio e rete dei server. This is a temporary measure, since the servers cannot set their own limits yet. Questa è una misura temporanea, in quanto i server non possono ancora impostare i loro limiti. Sorry for the inconvenience. Ci scusiamo per il disagio. Resolution Risoluzione Type Tipo Size Dimensioni %1 KiB of %2 KiB uploaded %1 KiB di %2 KiB caricati Invalid image Immagine non valida Setting a title helps make the Meanwhile feed more informative Impostare un titolo rende il feed laterale più informativo Add a brief title for the post (recommended) Aggiungi un breve titolo per il post (raccomandato) Remove Rimuovi Cancel the attachment, and go back to a regular note Rimuovi l'allegato e torna al post regolare Cc... Cc... Post verb Pubblica Note started from another application. Ignoring new note request from another application. &No, post to my followers only &No, rendilo visibile solo ai miei follower The image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. Il formato dell'immagine non è stato riconosciuto. L'estensione può essere sbagliata, come un'immagine GIF rinominata in immagine.jpg o altro. Select one audio file Seleziona un file audio Audio files Files audio Invalid audio file File audio non valido The audio format cannot be detected. Il formato del file audio non può essere rilevato. Select one video file Seleziona un file video Video files Files Video Invalid video file File video non valido The video format cannot be detected. Il formato del file video non può essere rilevato. Posting... Pubblicando... People... Persone... Select who will see this post Scegli chi potrà vedere il messaggio Hit Control+Enter to post with the keyboard Premi Control+Invio per pubblicare con la tastiera PumpController Creating person list... Creando la lista delle persone... Deleting person list... Eliminando la lista delle persone... Getting a person list... Ottenendo la lista della persona... Adding person to list... Aggiungendo una persona alla lista... Removing person from list... Rimuovendo una persona dalla lista... Creating group... Creando un gruppo... Joining group... Unendosi al gruppo... Leaving group... Lasciando il gruppo... Main timeline update requested, but updates are blocked. Richiesto un aggiornamento della timeline principale, ma gli aggiornamenti sono bloccati. Direct timeline update requested, but updates are blocked. Richiesto un aggiornamento dei messaggi diretti, ma gli aggiornamenti sono bloccati. Getting direct messages timeline... Ricevendo la timeline dei messaggi diretti... Activity timeline update requested, but updates are blocked. Richiesto un aggiornamento della timeline delle attività, ma gli aggiornamenti sono bloccati. Favorites timeline update requested, but updates are blocked. Richiesto un aggiornamento dei preferiti, ma gli aggiornamenti sono bloccati. Getting favorites timeline... Ricevendo la timeline dei preferiti... Timeline update requested, but updates are blocked. Richiesto un aggiornamento della Timeline, ma gli aggiornamenti son bloccati. Getting timeline... Ottenendo la timeline... Getting likes... Ricevendo i "mi piace"... Getting comments... Ricevendo i commenti... Getting minor feed... Ricevendo la timeline laterale... Getting '%1'... %1 is the name of a feed Ottenendo '%1'... Timeline Timeline Messages Messaggi Uploading %1 1=filename Caricando %1 HTTP error For the following HTTP error codesyou can check http://en.wikipedia.org/wiki/List_of_HTTP_status_codes in your language Errore HTTP Gateway Timeout HTTP 504 error string Gateway Timeout Service Unavailable HTTP 503 error string Servizio non Disponibile Not Implemented HTTP 501 error string Non implementato Internal Server Error HTTP 500 error string Errore Interno del Server Gone HTTP 410 error string Sparito Not Found HTTP 404 error string Non Trovato Forbidden HTTP 403 error string Proibito Unauthorized HTTP 401 error string Non Autorizzato Bad Request HTTP 400 error string Richiesta Errata Moved Temporarily HTTP 302 error string Spostato Temporaneamente Moved Permanently HTTP 301 error string Spostato Permanentemente Error connecting to %1 Errore durante la connessione a %1 Unhandled HTTP error code %1 Codice di errore HTTP non gestito: %1 Profile received. Profilo ricevuto. Followers Followers Following Following Profile updated. Profilo aggiornato. Post published successfully. Messaggio pubblicato con successo. Avatar published successfully. Avatar pubblicato con successo. Post updated successfully. Messaggio aggiornato con successo. Comment updated successfully. Commento aggiornato con successo. Comment posted successfully. Commento pubblicato con successo. 1 comment received. 1 commento ricevuto. %1 comments received. %1 commenti ricevuti. Post by %1 shared successfully. 1=author of the post we are sharing Post di %1 condiviso con successo. Received '%1'. %1 is the name of a feed Ricevuto '%1'. Adding items... Aggiungendo oggetti... SSL errors in connection to %1! Errori SSL durante la connessione a %1! OAuth error while authorizing application. Errore OAuth in fase di autorizzazione dell'appliazione. Minor feed received. Timeline laterale ricevuta. Main timeline Timeline principale Direct messages Messaggi diretti Activity Attività Favorites Preferiti Meanwhile Nel frattempo Mentions Menzioni Actions Azioni Getting '%1'... %1 is a feed's name Ottenendo '%1'... List of 'following' completely received. Lista dei 'following' ricevuta completamente. Partial list of 'following' received. Lista dei 'following' ricevuta parzialmente. List of 'followers' completely received. Lista dei 'followers' ricevuta completamente. Partial list of 'followers' received. Lista dei 'followers' ricevuta parzialmente. Person list deleted successfully. Lista di persone eliminata correttamente. Person list received. Lista di persone ricevuta. File downloaded successfully. File scaricato con successo. File uploaded successfully. Posting message... File caricato con successo. Pubblicando il messaggio... Timeline received. Updating post list... Timeline ricevuta. Aggiornando la lista dei messaggi... Authorized to use account %1. Getting initial data. Autorizzato ad utilizzare l'account %1. Ricevendo i dati iniziali. There is no authorized account. Non ci sono account autorizzati. Getting list of 'Following'... Ricevendo la lista dei 'Following'... Getting list of 'Followers'... Ricevendo la lista dei 'Followers'... Getting site users for %1... %1 is a server name Getting list of person lists... Rocevendo le liste delle persone... Getting main timeline... Ricevendo la timeline principale... Getting activity timeline... Ricevendo la timeline delle attività... The comments for this post cannot be loaded due to missing data on the server. User timeline E-mail updated: %1 %1 published successfully. Updating post content... %1 is the type of object: note, image... Untitled post %1 published successfully. %1 is a piece of the post (Jan) I tried my luck adjusting this one Messaggio senza titolo %1 pubblicato con successo. Post %1 published successfully. %1 is the title of the post (Jan) I tried my luck adjusting this one Messaggio %1 pubblicato con successo. Untitled post %1 updated successfully. %1 is a piece of the post (Jan) I tried my luck adjusting this one Messaggio senza titolo %1 aggiornato con successo. Post %1 updated successfully. %1 is the title of the post (Jan) I tried my luck adjusting this one Messaggio %1 aggiornato con successo. Comment %1 updated successfully. %1 is a piece of the comment (Jan) I tried my luck adjusting this one Commento %1 aggiornato con successo. Message liked or unliked successfully. Messaggio aggiunto o rimosso dai "Mi piace" correttamente. Likes received. "Mi piace" ricevuto. Comment %1 posted successfully. %1 is a piece of the comment (Jan) I tried my luck adjusting this one Commento %1 pubblicato con successo. Message deleted successfully. Messaggio cancellato correttamente. Following %1 (%2) successfully. %1 is a person's name, %2 is the ID Segui %1 (%2) con successo. Stopped following %1 (%2) successfully. %1 is a person's name, %2 is the ID Hai smesso di seguire %1 (%2) con successo. List of 'lists' received. Lista delle 'Liste' ricevuta. List of %1 users received. %1 is a server name Person list '%1' created successfully. Lista di persone '%1' creata con successo. %1 (%2) added to list successfully. 1=contact name, 2=contact ID %1 (%2) aggiunto alla lista con successo. %1 (%2) removed from list successfully. 1=contact name, 2=contact ID %1 (%2) rimosso dalla lista con successo. Group %1 created successfully. Gruppo %1 creato con successo. Group %1 joined successfully. Unito al gruppo %1 con successo. Left the %1 group successfully. Lasciato il gruppo %1 con successo. Avatar uploaded. Avatar caricato. Loading external image from %1 regardless of SSL errors, as configured... %1 is a hostname The application is not registered with your server yet. Registering... L'applicazione non è ancora autorizzata con il tuo server. Registrazione... Getting OAuth token... Ricevendo il token OAuth... OAuth support error Errore supporto OAuth Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support. La tua installazione di QOAuth, libreria usata da Dianara, sembra che non abbia il supporto HMAC-SHA1. You probably need to install the OpenSSL plugin for QCA: %1, %2 or similar. Probabilmente hai bisogno di installare il plugin OpenSSL per QCA: %1, %2 o simili. Authorization error Errore di Autorizzazione There was an OAuth error while trying to get the authorization token. C'è stato un errore OAuth nel tentativo di ottenere il token di autorizzazione. QOAuth error %1 Errore QOAuth %1 Application authorized successfully. Applicazione autorizzata con successo. Waiting for proxy password... In attesa della password del proxy... Still waiting for profile. Trying again... Aspettando ancora il tuo profilo. Provo ancora... %1 attempts 1 attempt Some initial data was not received. Restarting initialization... Alcuni dati iniziali non sono stati ricevuti. Riavvio inizializzazione... Some initial data was not received. Restarting initialization. Alcuni dati iniziali non sono stati ricevuti. Riavvio inizializzazione. Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally. Alcuni dati iniziali non sono stati ricevuti anche dopo diversi tentativi. Qualcosa potrebbe non funzionare sul tuo server. Potresti essere comunque in grado di usare il servizio normalmente. All initial data received. Initialization complete. Tutti i dati iniziali ricevuti. Inizializzazione completa. Ready. Pronto. SiteUsersList You can get a list of the newest users registered on your server by clicking the button below. More resources to find users: Wiki page 'Users by language' PPump user search service at inventati.org Get list of users from your server Close list Loading... %1 users in %2 %1 = user count, %2 = server name TimeLine Welcome to Dianara Benvenuto in Dianara Dianara is a <b>pump.io</b> client. Dianara è un client <b>pump.io</b>. Dianara is a <b>Pump.io</b> client. Dianara è un client <b>Pump.io</b>. Press <b>F1</b> if you want to open the Help window. Premi <b>F1</b> se vuoi aprire la finestra di Aiuto. First, configure your account from the <b>Settings - Account</b> menu. Prima di tutto, configura il tuo account nel menù <b>Configurazione - Account</b>. After the process is done, your profile and timelines should update automatically. Completato il processo, il tuo profilo e le timelines dovrebbero aggiornarsi automaticamente. Take a moment to look around the menus and the Configuration window. Prenditi un momento per dare uno sguardo ai menù ed alla finestra di Configurazione. You can also set your profile data and picture from the <b>Settings - Edit Profile</b> menu. Puoi anche impostare i dettagli e la foto del tuo profilo nel menu <b>Configurazione - Modifica profilo</b>. Dianara's blog Blog di Dianara Newest Ultimi Newer Più recenti Older Più vecchi Requesting... Loading... Page %1 of %2. Pagina %1 di %2. Showing %1 posts per page. Mostra %1 posts per pagina. %1 posts in total. %1 post in totale. Click here or press Control+G to jump to a specific page Clicca qui o premi Control+G per saltare ad una pagina specifica '%1' cannot be updated because a comment is currently being composed. %1 = feed's name '%1' non può essere aggiornato perchè un commento è in fase di composizione. %1 more posts pending for next update. Click here to receive them now. There are no posts Get %1 pending posts Ottieni %1 post in attesa If you don't have a Pump account yet, you can get one at the following address, for instance: se non hai ancora un account Pump, puoi averne uno al seguente indirizzo, per esempio: There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information. Ci sono tooltip ovunque, quindi se ti fermi col mouse sopra ad un bottone o ad un testo, probabilmente vedrai informazioni extra. Pump.io User Guide Guida Utente Pump.io Direct Messages Timeline Timeline dei messaggi diretti Here, you'll see posts specifically directed to you. Qui potrai vedere messaggi indirizzati specificatamente a te. Activity Timeline Timeline delle Attività You'll see your own posts here. Qui vedrai i tuoi messaggi. Favorites Timeline Timeline dei Preferiti Posts and comments you've liked. Messaggi e commenti che ti sono piaciuti. Timestamp Invalid timestamp! Timestamp non Valida! A minute ago Un minuto fa %1 minutes ago %1 minuti fa An hour ago Un'ora fa %1 hours ago %1 ore fa Just now In questo istante In the future Prossimamente Yesterday Ieri %1 days ago %1 giorni fa A month ago Un mese fa %1 months ago %1 mesi fa A year ago Un anno fa %1 years ago %1 anni fa UserPosts Posts by %1 Loading... &Close &Chiudi Received '%1'. Ricevuto '%1'. %1 posts Error loading the timeline dianara-v1.3.2/translations/dianara_fr.ts000644 000764 000764 00000553476 12614520347 020103 0ustar00janjan000000 000000 ASActivity Public %1 by %2 1=kind of object: note, comment, etc; 2=author's name ASObject Note Noun, an object type Article Noun, an object type Image Noun, an object type Audio Noun, an object type Video Noun, an object type File Noun, an object type Comment Noun, as in object type: a comment Group Noun, an object type Collection Noun, an object type Other As in: other type of post No detailed location Deleted on %1 and one other and %1 others ASPerson Hometown AccountDialog Your Pump.io address: Get &Verifier Code Verifier code: Enter or paste the verifier code provided by your Pump server here &Save Details If the browser doesn't open automatically, copy this address manually Account Configuration First, enter your Webfinger ID, your pump.io address. Your address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. If your profile is at https://pump.example/yourname, then your address is yourname@pump.example If you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. 1=link to website If you need help: %1 Pump.io User Guide Your address, like username@pumpserver.org After clicking this button, a web browser will open, requesting authorization for Dianara Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. Don't translate the VERIFIER word! &Authorize Application &Cancel Your account is properly configured. Press Unlock if you wish to configure a different account. &Unlock A web browser will start now, where you can get the verifier code Your Pump address is invalid Verifier code is empty Dianara is authorized to access your data AudienceSelector 'To' List 'Cc' List &Add to Selected All Contacts Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. ON THE LEFT should change to ON THE RIGHT in RTL languages Clear &List &Done &Cancel Selected People AvatarButton Open %1's profile in web browser Open your profile in web browser Send message to %1 Browse messages Stop following Follow Stop following? Are you sure you want to stop following %1? &Yes, stop following &No ColorPicker Change Choose a color Comment Posted on %1 Modified on %1 Like or unlike this comment Quote This is a verb, infinitive Reply quoting this comment Edit Modify this comment Delete Erase this comment Unlike Like %1 like this comment Plural: %1=list of people like John, Jane, Smith %1 likes this comment Singular: %1=name of just 1 person WARNING: Delete comment? Are you sure you want to delete this comment? &Yes, delete it &No CommenterBlock You can press Control+Enter to send the comment with the keyboard Reload comments Comment Infinitive verb Cancel Press ESC to cancel the comment if there is no text Check for comments Show all %1 comments Comments are not available Error: Already composing You can't edit a comment at this time, because another comment is already being composed. Editing comment Posting comment failed. Try again. Sending comment... Updating comment... Comment is empty. Composer Type a message here to post it Click here or press Control+N to post a note... Symbols Formatting Normal Bold Italic Underline Strikethrough Header List Table Preformatted block Quote block Make a link Insert an image from a web site Insert line &Format Button for text formatting and related options Text Formatting Options Paste Text Without Formatting Type a comment here Insert as image? The link you are pasting seems to point to an image. Insert as visible image Insert as link Table Size How many rows (height)? How many columns (width)? Insert a link Type or paste a web address here. You could also select some text first, to turn it into a link. Make a link from selected text Type or paste a web address here. The selected text (%1) will be converted to a link. Insert an image from a URL Type or paste the image address here. The link must point to the image file directly. Error: Invalid URL The address you entered (%1) is not valid. Image addresses should begin with http:// or https:// Cancel message? Are you sure you want to cancel this message? &Yes, cancel it &No ConfigDialog minutes Top Bottom Program Configuration Timeline &update interval posts Goes after a number, as: 25 posts &Posts per page, main timeline posts This goes after a number, like: 10 posts Posts per page, &other timelines &Tabs position &Movable tabs Public posts as &default Pro&xy Settings Network configuration Set Up F&ilters Filtering rules Highlighted activities, except mine Any highlighted activity Always Never Comments Left side tabs on left side/west; RTL not affected Right side tabs on right side/east; RTL not affected You are among the recipients of the activity, such as a comment addressed to you. Used also when highlighting posts addressed to you in the timelines. The activity is in reply to something done by you, such as a comment posted in reply to one of your notes. You are the object of the activity, such as someone adding you to a list. The activity is related to one of your objects, such as someone liking one of your posts. Used also when highlighting your own posts in the timelines. Item highlighted due to filtering rules. Item is new. Show snippets in minor feeds Show information for deleted posts Hide duplicated posts Jump to new posts line on update Avatar size Show extended share information Show extra information Highlight post author's comments Highlight your own comments Ignore SSL errors in images Show character counter Don't inform followers when following someone Don't inform followers when handling lists As system notifications Using own notifications Don't show notifications Notification Style Notify when receiving: New posts Highlighted posts New activities in minor feed Highlighted activities in minor feed Default System iconset, if available Show your current avatar Custom icon System Tray Icon &Type S&elect... Custom &Icon Hide window on startup Timelines Posts Composer Privacy Notifications System Tray This is a system notification System notifications are not available! Own notifications will be used. This is a basic notification Select custom icon Post Titles characters This is a suffix, after a number Snippet limit Post Contents Minor Feeds Only for images inserted from web sites. Use with care. Use attachment filename as initial post title Inform only the author when liking things General Options Fonts Colors Dianara stores data in this folder: &Save Configuration &Cancel Image files All files Invalid image The selected image is not valid. ContactCard Hometown Joined: %1 Updated: %1 Bio for %1 Abbreviation for Biography, but you can use the full word; %1=contact name This user doesn't have a biography No biography for %1 %1=contact name Open Profile in Web Browser Send Message Browse Messages In Lists... User Options Follow Stop Following Stop following? Are you sure you want to stop following %1? &Yes, stop following &No ContactList Type a partial name or ID to find a contact... F&ull List ContactManager username@server.org or https://server.org/username &Enter address to follow: &Follow Reload Followers Reload Following Export Followers Export Following Reload Lists &Neighbors Follo&wers Followin&g &Lists Export list of 'following' to a file Export list of 'followers' to a file DownloadWidget Download Save the attached file to your folders Cancel Save File As... All files Abort download? Do you want to stop downloading the attached file? &Yes, stop &No, continue Download aborted Download completed Download failed Downloading %1 KiB... %1 KiB downloaded EmailChanger Change E-mail Address Change &Cancel E-mail Address: Again: Your Password: E-mail addresses don't match! Password is empty! FilterEditor Filter Editor %1 if %2 contains: %3 This explains a filter rule, like: Hide if Author ID contains JohnDoe Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. Hide Highlight Post Contents Author ID Application Activity Description Keywords... &Add Filter Filters in use &Remove Selected Filter &Save Filters &Cancel if contains &New Filter C&urrent Filters FirstRunWizard Welcome Wizard Welcome to Dianara! This wizard will help you get started. You can access this window again at any time from the Help menu. The first step is setting up your account, by using the following button: Configure your &account Once you have configured your account, it's recommended that you edit your profile and add an avatar and some other information, if you haven't done so already. &Edit your profile By default, Dianara will post only to your followers, but it's recommended that you post to Public, at least sometimes. Post to &Public by default Open general program &help window &Show this again next time Dianara starts &Close FontPicker Change Choose a font HelpWidget Basic Help Getting started The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. At this point, your profile, contact lists and timelines will be loaded. You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. Settings You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. Timelines Contents Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. If you're new to Pump.io, take a look at this guide: Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. The main timeline, where you'll see all the stuff posted or shared by the people you follow. Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. Activity timeline, where you'll see your own posts, or posts shared by you. Favorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. Posting New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. It is possible to attach images, audio, video, and general files, like PDF documents, to your post. You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. If you add a specific person to the 'To' list, they will receive your message in their direct messages tab. Choose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. Managing contacts You can see the lists of people you follow, and who follow you from the Contacts tab. There, you can also manage person lists, used mainly to send posts to specific groups of people. You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. Keyboard controls Pump.io User Guide There are seven timelines: The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. LEFT SIDE should change to RIGHT SIDE on RTL languages The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). You can select who will see your post by using the To and Cc buttons. You can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. There is a text field at the top, where you can directly enter addresses of new contacts to follow them. You can also send a direct message (initially private) to that contact from this menu. You can find a list with some Pump.io users and other information here: Users by language The most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. Besides that, you can use: Control+Up/Down/PgUp/PgDown/Home/End to move around the timeline. Control+Left/Right to jump one page in the timeline. Control+G to go to any page in the timeline directly. Control+1/2/3 to switch between the minor feeds. Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. Dianara offers a D-Bus interface that allows some control from other applications. The interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. Command line options Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. Use the --debug parameter to have extra information in your terminal window, about what the program is doing. If your server does not support HTTPS, you can use the --nohttps parameter. If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. &Close ImageViewer Image &Save As... &Restart Animation &Close Save Image... Close Viewer Save Image As... Image files All files Error saving image There was a problem while saving %1. Filename should end in .jpg or .png extensions. ListsManager Name Members Add Mem&ber &Remove Member &Delete Selected List Add New &List Create L&ist &Add to List Are you sure you want to delete %1? 1=Name of a person list Remove person from list? Are you sure you want to remove %1 from the %2 list? 1=Name of a person, 2=name of a list &Yes Type a name for the new list... Type an optional description here WARNING: Delete list? &Yes, delete it &No LogViewer Log Clear &Log &Close MainWindow Side &Panel Status &Bar &Timeline The main timeline &Activity Your own posts Your favorited posts &Messages Messages sent explicitly to you &Contacts Initializing... Your account is not configured yet. Dianara started. Minor activities done by everyone, such as replying to posts Minor activities addressed to you Minor activities done by you The people you follow, the ones who follow you, and your person lists Press F1 for help Running with Qt v%1. Click here to configure your account &Session Auto-update &Timelines Mark All as Read &Post a Note &Quit &View &Toolbar Full &Screen &Log S&ettings Edit &Profile &Account Basic &Help Report a &Bug Pump.io User &Guide Some Pump.io &Tips List of Some Pump.io &Users Pump.io &Network Status Website Toolbar Open the log viewer Auto-updating enabled Auto-updating disabled Proxy password required You have configured a proxy server with authentication, but the password is not set. Enter the password for your proxy server: Starting automatic update of timelines, once every %1 minutes. Stopping automatic update of timelines. Received %1 older posts in '%2'. %1 is a number, %2 = name of a timeline 1 more pending to receive. singular, one post %1 more pending to receive. plural, several posts Also: Last update: %1 '%1' updated. %1 is the name of a feed Show Welcome Wizard 1 filtered out. singular, refers to a post %1 filtered out. plural, refers to posts 1 deleted. singular, refers to a post %1 deleted. plural, refers to posts There is 1 new activity. There are %1 new activities. 1 highlighted. singular, refers to an activity %1 highlighted. plural, refers to activities 1 filtered out. singular, refers to one activity %1 filtered out. plural, several activities No new activities. Error storing image! %1 bytes Marking everything as read... Closing due to environment shutting down... Quit? You are composing a note or a comment. Do you really want to close Dianara? &Yes, close the program &No Shutting down Dianara... System tray icon is not available. Dianara cannot be hidden in the system tray. Do you want to close the program completely? Timeline updated at %1. Update %1 Your Pump.io account is not configured Link to: %1 With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. English translation by JanKusanagi. TRANSLATORS: Change this with your language and name. If there was another translator before you, add your name after theirs ;) Dianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) &Configure Dianara &Filters and Highlighting &Help Visit &Website About &Dianara Your biography is empty Click to edit your profile 1 highlighted. singular, refers to a post %1 highlighted. plural, refers to posts No new posts. Total posts: %1 Favor&ites Received %1 older activities in '%2'. %1 is a number, %2 = name of feed Minor feed updated at %1. 1 more pending to receive. singular, 1 activity %1 more pending to receive. plural, several activities &Hide Window &Show Window There is 1 new post. There are %1 new posts. About Dianara Dianara is a pump.io social networking client. Thanks to all the testers, translators and packagers, who help make Dianara better! MinorFeed Older Activities Get previous minor activities There are no activities to show yet. Get %1 newer As in: Get 3 newer (activities) MinorFeedItem Using %1 Application used to generate this activity To: %1 1=people to whom this activity was sent Cc: %1 1=people to whom this activity was sent as CC Open referenced post MiscHelpers bytes PageSelector Jump to page Page number: &First As in: first page &Last As in: last page Newer As in: newer pages Older As in: older pages &Go &Cancel PeopleWidget &Search: Enter a name here to search for it Add a contact to a list &Cancel Post Like Like this post 1 like 1 comment Shared %1 times Shared on %1 Edited: %1 In Using %1 1=Program used for posting or sharing Share Edit Loading image... %1 likes %1 comments Delete Post Noun, not verb Via %1 Posted on %1 1=Date To If you select some text, it will be quoted. Unshare Unshare this post Open post in web browser Click to download the attachment Cc Copy post link to clipboard Normalize text colors &Close Type As in: type of object Modified on %1 Parent As in 'Open the parent post'. Try to use the shortest word! Open the parent post, to which this one replies Comment verb, for the comment button Reply to this post. Share this post with your contacts Modify this post Erase this post Join Group %1 members in the group Image is animated. Click on it to play. Couldn't load image! Attached Audio Attached Video Attached File %1 likes this One person %1 like this More than one person %1 shared this %1 = One person name %1 shared this %1 = Names for more than one person Shared once You like this Unlike Share post? Do you want to share %1's post? &Yes, share it &No Unshare post? Do you want to unshare %1's post? &Yes, unshare it WARNING: Delete post? Are you sure you want to delete this post? &Yes, delete it Click the image to see it in full size ProfileEditor Profile Editor This is your Pump address This is the e-mail address associated with your account, for things such as notifications and password recovery Change &E-mail... Change &Avatar... This is your visible name &Save Profile &Cancel Webfinger ID E-mail Avatar Full &Name &Hometown &Bio Not set In reference to the e-mail not being set for the account Select avatar image Image files All files Invalid image The selected image is not valid. ProxyDialog Proxy Configuration Do not use a proxy Your proxy username Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. &Save &Cancel Proxy &Type &Hostname &Port Use &Authentication &User Pass&word Publisher Title Select Picture... Find the picture in your folders Public Add a brief title for the post here (recommended) Remove Cancel the attachment, and go back to a regular note Followers Lists To... Select who will get a copy of this post Other as in other kinds of files Cancel Cancel the post Picture not set Select Audio File... Find the audio file in your folders Audio file not set Select Video... Find the video in your folders Video not set Select File... Find the file in your folders File not set Error: Already composing You can't edit a post at this time, because a post is already being composed. Update Editing post You can't create a message for %1 at this time, because a post is already being composed. Posting failed. Try again. Warning: You have no followers yet You're trying to post to your followers only, but you don't have any followers yet. If you post like this, no one will be able to see your message. Do you want to make the post public instead of followers-only? &Yes, make it public &Cancel, go back to the post Updating... Post is empty. File not selected. Select one image Image files Select one file Invalid file The file type cannot be detected. All files Since you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. File is too big Dianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. This is a temporary measure, since the servers cannot set their own limits yet. Sorry for the inconvenience. Resolution Type Size %1 KiB of %2 KiB uploaded Invalid image Setting a title helps make the Meanwhile feed more informative Cc... Post verb Note started from another application. Ignoring new note request from another application. &No, post to my followers only The image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. Select one audio file Audio files Invalid audio file The audio format cannot be detected. Select one video file Video files Invalid video file The video format cannot be detected. Posting... People... Select who will see this post Picture Audio Video Ad&d... Upload media, like pictures or videos Hit Control+Enter to post with the keyboard PumpController Creating person list... Deleting person list... Getting a person list... Adding person to list... Removing person from list... Creating group... Joining group... Leaving group... Getting likes... Getting comments... Activity Favorites Meanwhile Mentions Actions Uploading %1 1=filename HTTP error For the following HTTP error codesyou can check http://en.wikipedia.org/wiki/List_of_HTTP_status_codes in your language Gateway Timeout HTTP 504 error string Service Unavailable HTTP 503 error string Not Implemented HTTP 501 error string Internal Server Error HTTP 500 error string Gone HTTP 410 error string Not Found HTTP 404 error string Forbidden HTTP 403 error string Unauthorized HTTP 401 error string Bad Request HTTP 400 error string Moved Temporarily HTTP 302 error string Moved Permanently HTTP 301 error string Error connecting to %1 Unhandled HTTP error code %1 Profile received. Followers Following Profile updated. Comment %1 posted successfully. %1 is a piece of the comment Avatar published successfully. 1 comment received. %1 comments received. Post by %1 shared successfully. 1=author of the post we are sharing Adding items... Following %1 (%2) successfully. %1 is a person's name, %2 is the ID Stopped following %1 (%2) successfully. %1 is a person's name, %2 is the ID List of 'following' completely received. Partial list of 'following' received. List of 'followers' completely received. Partial list of 'followers' received. Person list deleted successfully. Person list received. File downloaded successfully. File uploaded successfully. Posting message... OAuth error while authorizing application. Authorized to use account %1. Getting initial data. There is no authorized account. Getting list of 'Following'... Getting list of 'Followers'... Getting site users for %1... %1 is a server name Getting list of person lists... The comments for this post cannot be loaded due to missing data on the server. Getting '%1'... %1 is the name of a feed Timeline Messages User timeline E-mail updated: %1 %1 published successfully. Updating post content... %1 is the type of object: note, image... Untitled post %1 published successfully. %1 is a piece of the post Post %1 published successfully. %1 is the title of the post Untitled post %1 updated successfully. %1 is a piece of the post Post %1 updated successfully. %1 is the title of the post Comment %1 updated successfully. %1 is a piece of the comment Message liked or unliked successfully. Likes received. Received '%1'. %1 is the name of a feed Message deleted successfully. List of 'lists' received. List of %1 users received. %1 is a server name Person list '%1' created successfully. %1 (%2) added to list successfully. 1=contact name, 2=contact ID %1 (%2) removed from list successfully. 1=contact name, 2=contact ID Group %1 created successfully. Group %1 joined successfully. Left the %1 group successfully. Avatar uploaded. SSL errors in connection to %1! Loading external image from %1 regardless of SSL errors, as configured... %1 is a hostname The application is not registered with your server yet. Registering... Getting OAuth token... OAuth support error Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support. You probably need to install the OpenSSL plugin for QCA: %1, %2 or similar. Authorization error There was an OAuth error while trying to get the authorization token. QOAuth error %1 Application authorized successfully. Waiting for proxy password... Still waiting for profile. Trying again... %1 attempts 1 attempt Some initial data was not received. Restarting initialization... Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally. All initial data received. Initialization complete. Ready. SiteUsersList You can get a list of the newest users registered on your server by clicking the button below. More resources to find users: Wiki page 'Users by language' PPump user search service at inventati.org Get list of users from your server Close list Loading... %1 users in %2 %1 = user count, %2 = server name TimeLine Welcome to Dianara Dianara is a <b>Pump.io</b> client. If you don't have a Pump account yet, you can get one at the following address, for instance: Press <b>F1</b> if you want to open the Help window. First, configure your account from the <b>Settings - Account</b> menu. After the process is done, your profile and timelines should update automatically. Take a moment to look around the menus and the Configuration window. You can also set your profile data and picture from the <b>Settings - Edit Profile</b> menu. There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information. Dianara's blog Pump.io User Guide Direct Messages Timeline Here, you'll see posts specifically directed to you. Activity Timeline You'll see your own posts here. Favorites Timeline Posts and comments you've liked. Newest Newer Older Requesting... Loading... Page %1 of %2. Showing %1 posts per page. %1 posts in total. Click here or press Control+G to jump to a specific page '%1' cannot be updated because a comment is currently being composed. %1 = feed's name %1 more posts pending for next update. Click here to receive them now. There are no posts Timestamp Invalid timestamp! A minute ago %1 minutes ago An hour ago %1 hours ago Just now In the future Yesterday %1 days ago A month ago %1 months ago A year ago %1 years ago UserPosts Posts by %1 Loading... &Close Received '%1'. %1 posts Error loading the timeline dianara-v1.3.2/translations/dianara_de.ts000644 000764 000764 00000644326 12614520617 020057 0ustar00janjan000000 000000 ASActivity Public Öffentlich %1 by %2 1=kind of object: note, comment, etc; 2=author's name %1 von %2 ASObject Note Noun, an object type passende Übersetzung von dict.leo.org Mitteilung Article Noun, an object type Artikel Image Noun, an object type Bild Audio Noun, an object type Audio Video Noun, an object type Video File Noun, an object type Datei Comment Noun, as in object type: a comment Kommentar Group Noun, an object type Gruppe Collection Noun, an object type Sammlung Other As in: other type of post Sonstiges No detailed location kein genauer Ort Deleted on %1 Gelöscht am %1 and one other Kommt darauf an ob es nur um weitere 'Kommentare' oder auch um 'Notizen' o.ä. geht. und ein(e) Weitere(r) and %1 others und %1 weitere ASPerson Hometown Heimatort AccountDialog Your Pump.io address: Ihre Pump.io Adresse: Webfinger ID, like username@pumpserver.org Webfinger ID, z.B. benutzer@pumpserver.org Your address, as username@server Ihre Adresse, als benutzer@server Get &Verifier Code &Verifizierungskode erhalten Verifier code: Verifizierungskode: Enter or paste the verifier code provided by your Pump server here (Jan) This should be updated with the new "or paste" part Geben Sie hier den von Ihrem Pump-Server bereitgestellten Verifizierungskode ein &Save Details Daten &speichern If the browser doesn't open automatically, copy this address manually Falls der Browser sich nicht automatisch öffnet, kopieren Sie die folgende Adresse von Hand Account Configuration Konto konfigurieren First, enter your Webfinger ID, your pump.io address. Geben Sie zuerst Ihre Webfinger ID (Ihre Pump.io Adresse) ein. Your address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. Ihre Adresse sieht aus wie 'Benutzer@pumpeserver.org'. Sie können diese über die Web-Oberfläche in Ihrem Profil finden. If your profile is at https://pump.example/yourname, then your address is yourname@pump.example Für ein Profil auf 'https://pump.beispiel/benutzer', ist Ihre zugehörige Adresse 'benutzer@pump.beispiel' If you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. 1=link to website If you need help: %1 Pump.io User Guide Pump.io Leitfaden Your address, like username@pumpserver.org (Jan) took this one from another string that said "as username@..."; it might need a different translation here Ihre Adresse, als benutzer@server After clicking this button, a web browser will open, requesting authorization for Dianara Nachdem Sie diese Schaltfläche gedrückt haben, wird sich ein Webbrowser öffnen, um die Autorisierung für Dianara anzufordern Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. Don't translate the VERIFIER word! Sobald Sie Dianara von der Oberfläche Ihres Pump-Servers aus autorisiert haben, werden Sie einen Code namens 'VERIFIER' erhalten. Kopieren Sie diesen und fügen Sie ihn in das Feld unterhalb ein. Enter the verifier code provided by your Pump server here Geben Sie hier den von Ihrem Pump-Server bereitgestellten Verifizierungskode ein Paste the verifier here Fügen Sie den VERIFIER hier ein &Authorize Application Anwendung &autorisieren &Cancel &Abbrechen Your account is properly configured. Press Unlock if you wish to configure a different account. &Unlock A web browser will start now, where you can get the verifier code Ein Browser wird jetzt gestartet, um den Verifizierungskode zu erhalten Your Pump address is invalid Ihre Pump-Adresse ist ungültig Verifier code is empty Verifizierungskode ist leer Dianara is authorized to access your data Dianara hat autorisierten Zugang zu Ihre Daten AudienceSelector 'To' List Empfänger Liste 'CC' List "CC-Addressees" List... It's the standard way to translate this CC-Empfänger Liste 'Cc' List Cc-Empfänger Liste &Add to Selected unsicher über Position von '&' Zur Auswahl &hinzufügen All Contacts Alle Kontakte Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. Wählen Sie Kontakte aus der Liste links aus. Sie können diese mit der Maus ziehen, einfach oder doppelt anklicken oder sie auswählen und die Schaltfläche unten verwenden. Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. ON THE LEFT should change to ON THE RIGHT in RTL languages Wählen Sie Kontakte aus der Liste links aus. Sie können diese mit der Maus ziehen, einfach oder doppelt anklicken oder sie auswählen und die Schaltfläche unten verwenden. Clear &List &Liste leeren &Done &Fertig &Cancel &Abbrechen Selected People Ausgewählte Kontakte AvatarButton Open %1's profile in web browser Profil von %1 im Browser öffnen Open your profile in web browser Ihres Profil im Browser öffnen Send message to %1 Sende eine Nachricht an %1 Browse messages Stop following Nicht mehr folgen Follow Folgen Stop following? Nicht mehr folgen? Are you sure you want to stop following %1? Sind Sie sicher, dass Sie %1 nicht mehr folgen wollen? &Yes, stop following &Ja, nicht mehr folgen &No &Nein ColorPicker Change Wechseln Choose a color Comment Posted on %1 Am %1 veröffentlicht Modified on %1 Am %1 verändert Like or unlike this comment Diesen Kommentar favorisieren / defavorisieren Quote This is a verb, infinitive Zitieren Reply quoting this comment Antwort mit Zitat dieses Kommentars Edit Bearbeiten Modify this comment Diesen Kommentar bearbeiten Delete Löschen Erase this comment Diesen Kommentar löschen Unlike Defavorisieren Like Favorisieren %1 like this comment Plural: %1=list of people like John, Jane, Smith %1 gefällt dieser Kommentar %1 likes this comment Singular: %1=name of just 1 person %1 gefällt dieser Kommentar WARNING: Delete comment? WARNUNG: Kommentar löschen? Are you sure you want to delete this comment? Sind Sie sicher, dass Sie diesen Kommentar löschen wollen? &Yes, delete it &Ja, löschen &No &Nein CommenterBlock You can press Control+Enter to send the comment with the keyboard Sie können Strg+Eingabe auf der Tastatur drücken, um den Kommentar abzusenden &Cancel &Abbrechen Reload comments Kommentare neu laden Comment Infinitive verb Kommentieren Cancel Abbrechen Press ESC to cancel the comment if there is no text Drücken Sie ESC um den Kommentar abzubrechen, wenn das Textfeld leer ist Check for comments Show all %1 comments Zeige alle %1 Kommentare Comments are not available Error: Already composing Fehler: Schon am Kommentieren You can't edit a comment at this time, because another comment is already being composed. Sie können jetzt keinen Kommentar bearbeiten, weil bereits ein anderer Kommentar erstellt wird. Editing comment Kommentar bearbeiten Posting comment failed. Try again. Veröffentlichung des Kommentars schlug fehl. Versuchen Sie es noch einmal. Sending comment... Kommentar wird gesendet... Updating comment... Kommentar wird aktualisiert... Comment is empty. Kommentar ist leer. Composer Type a message here to post it "Type your post here" Schreiben Sie hier Ihren Beitrag Click here or press Control+N to post a note... Klicken Sie hier oder drücken Sie Strg+N um eine Mitteilung zu schreiben... Symbols Symbole Formatting Format Normal Normal Bold Fett Italic Kursiv Underline Unterstrichen Strikethrough Durchgestrichen Header Vorspann List Liste Table Tabelle Preformatted block Vorformattierter Bereich Quote block Zitat Make a link Insert link Link hinzufügen Insert an image from a web site Bild von einer Webseite hinzufügen Insert line Linie hinzufügen &Format Button for text formatting and related options &Format Text Formatting Options Textformateinstellungen Paste Text Without Formatting Text ohne Formatierung einfügen Type a comment here Geben Sie hier einen Kommentar ein Insert as image? Als Bild einfügen? The link you are pasting seems to point to an image. Der Link den Sie gerade einfügen scheint auf eine Bilddatei zu zeigen. Insert as visible image Als sichtbares Bild einfügen Insert as link Als Link einfügen Table Size Tabellengröße How many rows (height)? Wie viele Zeilen (Höhe)? How many columns (width)? Wie viele Spalten (Breite)? Insert a link Link hinzufügen Type or paste a web address here. You could also select some text first, to turn it into a link. Tippen oder fügen Sie hier eine Webadresse ein. Sie können auch zuerst Text auswählen, um diesen in einen Link umzuwandeln. Make a link from selected text Link aus ausgewähltem Text erstellen Type or paste a web address here. The selected text (%1) will be converted to a link. Tippen oder fügen Sie hier eine Webadresse ein. Der ausgewählte Text (%1) wird in einen Link umgewandelt. Insert an image from a URL Fügen Sie ein Bild aus einer URL ein Type or paste the image address here. The link must point to the image file directly. Tippen oder fügen Sie hier die Adresse für ein Bild ein. Dieser Link muss direkt auf die Bilddatei verweisen. Error: Invalid URL Fehler: Ungültige URL The address you entered (%1) is not valid. Image addresses should begin with http:// or https:// "Addresses should begin with..." Die Adresse, dass Sie hinzugefügt haben (%1) ist nicht gültig. Adressen sollen mit http:// oder https:// anfangen Cancel message? "Discard message?" Beitrags verwerfen? Are you sure you want to cancel this message? "(...) discard this message?" Sind Sie sicher, dass Sie diesen Beitrag verwerfen wollen? &Yes, cancel it &Ja, abbrechen &No &Nein ConfigDialog minutes Minuten Top Oben Bottom Unten Left side Linke Seite Right side Rechte Seite Program Configuration Programmeinstellungen Timeline &update interval Akt&ualisierungsintervall der Zeitleiste posts Goes after a number, as: 25 posts Beiträge &Posts per page, main timeline Beiträge &pro Seite, Haupt-Zeitleiste posts This goes after a number, like: 10 posts Beiträge Posts per page, &other timelines Beiträge pr&o Seite, andere Zeitleisten &Tabs position Stelle für &Tableiste &Movable tabs Be&wegliche Tableiste Public posts as &default Öffentliche Beiträge als Stan&dard Pro&xy Settings Pro&xy-Einstellungen Network configuration Netzwerkkonfiguration Set Up F&ilters F&ilter einrichten Filtering rules Filterregeln Minor Feeds (Jan) need to update it to plural Nebenfeed Highlighted activities, except mine Hervorgehobene Aktivitäten, außer meine Any highlighted activity Alle hervorgehobenen Aktivitäten Always Immer Never Nie Show snippets in minor feed Schnipsel im Nebenfeed zeigen characters This is a suffix, after a number Zeichen Snippet limit Maximale Schnipsel-Länge Post Titles "Titles of Posts" Beitragstitel Post Contents "Contents of Posts" Beitragsinhalte Comments Kommentare Minor Feed Nebenfeed You are among the recipients of the activity, such as a comment addressed to you. Sie sind unter den Empfängern der Aktivität, z.B. eines Kommentars, der an Sie gerichtet ist. Used also when highlighting posts addressed to you in the timelines. "Is also used to highlight posts addressed to you in the timelines." Wird auch verwendet, um an Sie gerichtete Beiträge in den Zeitleisten hervorzuheben. The activity is in reply to something done by you, such as a comment posted in reply to one of your notes. Die Aktivität ist eine Reaktion auf eine Ihrer Aktionen, z.B. ein Kommentar, der als Antwort auf eine Ihrer Mitteilungen veröffentlicht wurde. You are the object of the activity, such as someone adding you to a list. "(...), e.g. if you've been added to a list." Sie sind das Ziel dieser Aktivität, z.B. , wenn Sie zu einer Liste hinzugefügt wurden. The activity is related to one of your objects, such as someone liking one of your posts. Die Aktivität bezieht sich auf eines Ihrer Objekte, z.B., wenn einer Ihrer Beiträge favorisiert wurde. Used also when highlighting your own posts in the timelines. Wird auch verwendet, um Ihre eigenen Beiträge in den Zeitleisten hervorzuheben. Item highlighted due to filtering rules. Element wurde wegen einer Filterregel hervorgehoben. Item is new. Element ist neu. Show snippets in minor feeds (Jan) Needs to be updated to plural, "feeds" Schnipsel im Nebenfeed zeigen Show information for deleted posts Hide duplicated posts Jump to new posts line on update Avatar size (Jan) I tried my luck xD Avatargröße Show extended share information Show extra information Zusätzliche Informationen anzeigen Highlight post author's comments Highlight your own comments Ignore SSL errors in images Show character counter Don't inform followers when following someone Don't inform followers when handling lists As system notifications Als Systembenachrichtigungen Using own notifications "Use own notification system" Eigenes Benachrichtigungssytem benutzen Don't show notifications Keine Benachrichtigungen zeigen Notification Style Stil der Benachrichtigungen Notify when receiving: Benachrichtigen beim Empfang von: New posts translated as completion of the sentence "Notify when receiving: New Posts" neuen Beiträgen Highlighted posts translated as completion of the sentence "Notify when receiving: Highlighted posts" hervorgehobenen Beiträgen New activities in minor feed translated as completion of the sentence "Notify when receiving: New activities in minor feed" neuen Aktivitäten im Nebenfeed Highlighted activities in minor feed hervorgehobenen Aktivitäten im Nebenfeed Default Standard System iconset, if available Systemeigenes Iconset, falls verfügbar Show your current avatar Ihren derzeitigen Avatar zeigen Custom icon Benutzerdefiniertes Symbol System Tray Icon &Type Symbol&typ fürs System-Benachrichtigungsfeld S&elect... Aus&wählen... Custom &Icon Benutzerdef&iniertes Symbol Hide window on startup General Options Allgemeine Optionen Fonts Schriftarten Colors Farben Timelines Zeitleisten Posts Beiträge Composer Privacy Notifications Benachrichtigungen System Tray System-Benachrichtigungsfeld This is a system notification System notifications are not available! Own notifications will be used. This is a basic notification Select custom icon Benutzerdefiniertes Symbol auswählen Dianara stores data in this folder: Dianara speichert Daten in diesen Ordner: Left side tabs on left side/west; RTL not affected Linke Seite Right side tabs on right side/east; RTL not affected Rechte Seite Only for images inserted from web sites. Use with care. Use attachment filename as initial post title Inform only the author when liking things &Save Configuration Konfiguration &speichern &Cancel &Abbrechen Image files Bilddateien All files Alle Dateien Invalid image Ungültige Bilddatei The selected image is not valid. Die ausgewählte Bilddatei ist nicht gültig. ContactCard Hometown Heimatort Joined: %1 Beigetreten: %1 Updated: %1 Aktualisiert: %1 Bio for %1 Abbreviation for Biography, but you can use the full word; %1=contact name Biographie von %1 This user doesn't have a biography Dieser Benutzer hat keine Biographie No biography for %1 %1=contact name Keine Biographie für %1 Open Profile in Web Browser Profil im Browser öffnen Send Message Nachricht senden Browse Messages In Lists... In den Listen... User Options Benutzereinstellungen Follow Folgen Stop Following Nicht mehr folgen Stop following? Nicht mehr folgen? Are you sure you want to stop following %1? Sind Sie sich sicher, dass Sie %1 nicht mehr folgen wollen? &Yes, stop following &Ja, nicht mehr folgen &No &Nein ContactList Type a partial name or ID to find a contact... Tippen Sie ein Stück eines Namens oder einer ID ein, um einen Kontakt zu finden... F&ull List &Komplette Liste ContactManager username@server.org or https://server.org/username benutzer@server.org oder https://server.org/benutzer &Enter address to follow: Literally, "enter user address", the follow button already gives enough context to understand what is this about &Benutzeraddresse eingeben: &Follow &Folgen Reload Followers 'Anhängerschaft' aktualisieren Reload Following 'Verfolgte' aktualisieren Export Followers 'Anhängerschaft' exportieren Export Following 'Verfolgte' exportieren Reload Lists Listen auffrischen &Neighbors Follo&wers &Anhängerschaft Followin&g Verfol&gte &Lists &Listen Export list of 'following' to a file Exportiere Liste der 'Verfolgten' in eine Datei Export list of 'followers' to a file Exportiere Liste der 'Anhängerschaft' in eine Datei DownloadWidget Download Herunterladen Save the attached file to your folders Angehängte Datei in einen Ordner speichern Cancel Abbrechen Save File As... Datei speichern unter... All files Alle Dateien Abort download? Herunterladen abbrechen? Do you want to stop downloading the attached file? Wollen Sie das Herunterladen der angehängten Datei anhalten? &Yes, stop &Ja, anhalten &No, continue &Nein, fahre fort Download aborted Herunterladen abgebrochen Download completed Herunterladen abgeschlossen Download failed Herunterladen fehlgeschlagen Downloading %1 KiB... Lade %1 KiB herunter... %1 KiB downloaded %1 KiB heruntergeladen EmailChanger Change E-mail Address Change Wechseln &Cancel &Abbrechen E-mail Address: Again: Your Password: E-mail addresses don't match! Password is empty! FilterEditor Filter Editor Filter-Editor %1 if %2 contains: %3 This explains a filter rule, like: Hide if Author ID contains JohnDoe %1, wenn %2 %3 enthält Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. Hier können Sie einige Regeln setzen, um Elemente zu verstecken oder hervorzuheben. Sie können nach Inhalt, Autor oder Anwendung filtern. Zum Beispiel können Sie Beiträge herausfiltern, welche von der Anwendung 'Open Farm Game' stammen, oder welche das Wort 'NSFW' enthalten. Sie können auch Beiträge hervorheben, die Ihren Namen enthalten. Hide Verstecken Highlight Hervorheben Post Contents Beitragsinhalt Author ID Autor-ID Application Anwendung Activity Description Aktivitätsbeschreibung Keywords... Kennwörter... &Add Filter Filter &hinzufügen Filters in use Filter in Benutzung &Remove Selected Filter singular Ausgewählten Filter entfe&rnen &Save Filters Filter &speichern &Cancel &Abbrechen if wenn contains enthält &New Filter Lit. "Create new filter" in order to avoid having a bare accusative. &Neuen Filter erstellen C&urrent Filters &Aktuelle Filter FirstRunWizard Welcome Wizard Welcome to Dianara! This wizard will help you get started. You can access this window again at any time from the Help menu. The first step is setting up your account, by using the following button: Configure your &account Once you have configured your account, it's recommended that you edit your profile and add an avatar and some other information, if you haven't done so already. &Edit your profile By default, Dianara will post only to your followers, but it's recommended that you post to Public, at least sometimes. Post to &Public by default Open general program &help window &Show this again next time Dianara starts &Close FontPicker Change Wechseln Choose a font HelpWidget Basic Help Hilfe zu Grundlagen Getting started Loslegen The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. consider the translations in category 'AccountDialog' Wenn Sie Dianara zum ersten Mal starten, sollten Sie den Konten-Konfigurationsdialog sehen. Geben Sie dort Ihre Pump.io-Adresse in der Form 'Name@Server' ein und drücken Sie die Schaltfläche 'Verifizierungkode erhalten'. Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. consider the translations in category 'AccountDialog' Dann sollte Ihr üblicher Browser die Autorisierungsseite Ihres Pump.io-Servers laden. Dort werden Sie den ganzen 'VERIFIER'-Code kopieren und diesen dann in das zweite Feld bei Dianara einfügen müssen. Drücken Sie daraufhin 'Anwendung autorisieren' und - sobald dies bestätigt ist - 'Daten speichern'. At this point, your profile, contact lists and timelines will be loaded. Zu diesem Zeitpunkt werden Ihr Profil, Ihre Kontaklisten und Ihre Zeitleisten geladen werden. You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. Sie sollten einen Blick in das Fenster 'Programmeinstellungen' werfen, erreichbar über das Menü 'Einstellungen' - 'Dianara einrichten'. Dort finden Sie einige interessante Optionen. Settings Einstellungen You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. In den Einstellungen können Sie viele Dinge nach Ihren Wünschen anpassen, darunter das Zeitintervall zwischen Zeitleisten-Aktualisierungen, die Anzahl Beiträge, die Sie pro Seite angezeigt bekommen, die Farben für Hervorhebungen, die Benachrichtigungen oder das Aussehen des Symbols im System-Benachrichtigungsfeld. Timelines Zeitleisten Contents Inhalt Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. Vergessen Sie nicht, dass es in Dianara viele Stellen gibt, wo Sie zusätzliche Informationen erhalten können, indem Sie Textbereiche oder Schaltflächen mit Ihrer Maus überfahren und auf das Erscheinen eines Tooltips achten. If you're new to Pump.io, take a look at this guide: Falls Sie neu bei Pump.io sind, schauen Sie sich diesen Leitfaden an: Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. Dort können Sie auch die Option, immer 'öffentlich' zu veröffentlichen, aktivieren. Ansonsten können Sie dies immer im Augenblick des Veröffentlichens bestimmen. The main timeline, where you'll see all the stuff posted or shared by the people you follow. Die Haupt-Zeitleiste, in der Sie all das Zeug sehen werden, das von den Leuten, denen Sie folgen veröffentlicht oder geteilt wird. Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. Die Nachrichten-Zeitleiste, in der Sie Nachrichten sehen werden, die direkt an Sie gerichtet sind. Diese Nachrichten können auch an andere Leute gesendet worden sein. Activity timeline, where you'll see your own posts, or posts shared by you. Die 'Aktivität'-Zeitleiste, in der Sie Ihre eigenen oder von Ihnen geteilte Beiträge sehen werden. Favorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. Die Favoriten-Zeitleiste, in der Sie die Beiträge und Kommentare sehen werden, die Sie favorisiert haben (indem Sie "Gefällt mir" gedrückt haben). Dieses kann als Lesezeichensystem verwendet werden. The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. Die fünfte Zeitleiste ist der Nebenfeed, auch als 'Inzwischen' bezeichnet. Dieser ist auf der linken Seite zu sehen, doch kann er auch versteckt werden. Dort werden Sie Nebenaktivitäten von allen, denen Sie folgen, sehen, darunter Kommentar-Aktionen, Favorisieren von Beiträgen oder Leuten zu folgen. These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. Diese Aktivitäten können eine '+'-Schaltfläche enthalten. Drücken Sie darauf, um den erwähnten Beitrag zu öffnen. Außerdem können Sie, wie an vielen anderen Stellen, durch Überfahren mit der Maus relevante Informationen sehen. Posting Veröffentlichen New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. Neue Beiträge erscheinen andersfarbig hervorgehoben. Sie können sie einfach als 'gelesen' markieren, indem Sie auf irgendeine freie Stelle innerhalb des Beitrags klicken. You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. Sie können Mitteilungen veröffentlichen, indem Sie in das Eingabefeld im oberen Fensterbereich klicken oder Strg+N drücken. Ihrem Beitrag einen Titel zu geben ist optional, jedoch sehr empfehlenswert, da es das Erkennen von Verweisen auf Ihren Beitrag im Nebenfeed, in Email-Benachrichtigungen usw., vereinfacht. It is possible to attach images, audio, video, and general files, like PDF documents, to your post. Es ist möglich Bilder, Audio- und Videodateien sowie allgemeine Dateien (wie PDF-Dokumente) an Ihren Beitrag anzuhängen. You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. Mittels der 'Format' Schaltfläche können Sie Ihrem Text Formatierungen wie Fett- oder Kursivdruck verleihen. Manche dieser Optionen setzen eine Textauswahl voraus. You can select who will see your post by using the To and CC buttons. Indem Sie die 'An'- und die 'CC'-Schaltfläche verwenden, bestimmen Sie wer Ihren Beitrag sehen wird. If you add a specific person to the 'To' list, they will receive your message in their direct messages tab. Wenn Sie der 'An'-Liste eine bestimmte Person beifügen, wird diese Ihren Beitrag über ihr Direktnachrichten-Tab empfangen. You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. Sie können private Nachrichten erstellen, indem Sie bestimmte Personen zu diesen Listen hinzufügen und die Optionen 'Anhängerschaft' und 'öffentlich' abwählen. Managing contacts Kontakte verwalten You can see the lists of people you follow, and who follow you from the Contacts tab. Sie können die Listen der Leute, denen Sie folgen, und derjenigen, die Ihnen folgen, im Kontakte-Tab anzeigen lassen. There, you can also manage person lists, used mainly to send posts to specific groups of people. Dort können Sie auch Personenlisten verwalten, die in erster Linie dazu verwendet werden, Beiträge an bestimmte Personenkreise zu senden. You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. Sie können auf alle Avatare in Beiträgen, Kommentaren und der 'Inzwischen'-Spalte klicken, woraufhin ein Menü mit mehreren Optionen erscheint; eine davon das Folgen bzw. Nicht-mehr-folgen der betreffenden Person. Keyboard controls Tastaturbefehle Pump.io User Guide Pump.io Leitfaden There are seven timelines: Es gibt sieben Zeitleisten: The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. LEFT SIDE should change to RIGHT SIDE on RTL languages Die fünfte Zeitleiste ist der Nebenfeed, auch als 'Inzwischen' bezeichnet. Dieser ist auf der linken Seite zu sehen, doch kann er auch versteckt werden. Dort werden Sie Nebenaktivitäten von allen, denen Sie folgen, sehen, darunter Kommentar-Aktionen, Favorisieren von Beiträgen oder Leuten zu folgen. The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). Die sechste und siebte Zeitleiste sind ebenfalls Nebenfeeds, ähnlich wie 'Inzwischen', enthalten aber nur Aktivitäten, die direkt Sie betreffen (Erwähnungen), und Aktionen durch Sie (Aktionen). You can select who will see your post by using the To and Cc buttons. Indem Sie die 'An'- und die 'Cc'-Schaltfläche verwenden, bestimmen Sie wer Ihren Beitrag sehen wird. You can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. There is a text field at the top, where you can directly enter addresses of new contacts to follow them. You can also send a direct message (initially private) to that contact from this menu. Sie können diesem Kontakt aus dem selben Menü auch eine (anfangs private) Direktnachricht senden. You can find a list with some Pump.io users and other information here: Sie finden eine Liste mit einigen Pump.io-Nutzern und anderen Informationen hier: Users by language The most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. Die geläufigsten Befehle in den Menüs sind durch Tastaturkürzel, wie 'F5' oder 'Strg+N', ergänzt. Besides that, you can use: Außerdem können Sie Folgendes benutzen: Control+Up/Down/PgUp/PgDown/Home/End to move around the timeline. Strg+Hoch/Runter/BildAuf/BildAb/Pos1/Ende, um sich über die Zeitleiste zu bewegen. Control+Left/Right to jump one page in the timeline. Strg+Links/Rechts, um eine Seite in der Zeitleiste zu springen. Control+G to go to any page in the timeline directly. Str+G, um direkt zu einer bestimmten Seite der Zeitleiste zu springen. Control+1/2/3 to switch between the minor feeds. Strg+1/2/3, um zwischen den Nebenfeeds zu wechseln. Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. Strg+Eingabe, um zu veröffentlichen, wenn Sie das Erstellen eines Beitrags oder Kommentars abgeschlossen haben. Falls der Beitrag leer ist, können Sie ihn mit ESC verwerfen. Dianara offers a D-Bus interface that allows some control from other applications. The interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. Command line options Kommandozeilenoptionen Choose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. Während des Erstellens einer Mitteilung können Sie mithilfe der Eingabetaste vom Titel- ins Text-Feld springen. Umgekehrt können Sie mithilfe der Pfeiltaste aufwärts vom Text- zurück ins Titel-Feld springen, wenn sich der Cursor ganz am Anfang des Feldes befindet. Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. Sie können den Parameter '--config' verwenden um das Programm mit einer anderen Konfiguration auszuführen. Das kann nützlich sein, um zwei oder mehr Konten zu verwenden. Sie können sogar zwei Instanzen Dianaras gleichzeitig ausführen. Use the --debug parameter to have extra information in your terminal window, about what the program is doing. Verwenden Sie den Parameter '--debug', um zusätzliche Informationen über das Verhalten des Programms in Ihrem Terminal anzuzeigen. If your server does not support HTTPS, you can use the --nohttps parameter. If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. &Close S&chließen ImageViewer Image Bild Resolution and file size Auflösung und Dateigröße ESC to close, secondary-click for options ESC um zu schließen, Rechtsklick für Optionen &Save As... &Speichern unter... &Restart Animation Animation &neu starten &Close &Schließen Save Image... Bild speichern... Close Viewer Betrachter schließen Save Image As... Bild speichern unter... Image files Bilddateien All files Alle Dateien Error saving image Fehler beim speichern des Bildes There was a problem while saving %1. Filename should end in .jpg or .png extensions. Ein Fehler ist aufgetreten beim Speichern von %1. Der Dateiname sollte mit den Erweiterungen '.jpg' oder '.png' enden. ListsManager Name Name Members Mitglieder Add Mem&ber Mitglied &hinzufügen &Remove Member Mitglied &löschen &Delete Selected List Ausgewählte Li&ste löschen Add New &List &Neue Liste hinzufügen Create L&ist Liste &erstellen &Add to List Zur Liste hin&zufügen Are you sure you want to delete %1? 1=Name of a person list Lit. "Do you want to delete %1?" Sind Sie sich sicher, dass Sie %1 löschen wollen? Remove person from list? Person aus der Liste löschen? Are you sure you want to remove %1 from the %2 list? 1=Name of a person, 2=name of a list Sind Sie sich sicher, dass Sie %1 aus der Liste %2 löschen wollen? &Yes &Ja Type a name for the new list... Geben Sie einen Namen für die neue Liste ein... Type an optional description here Geben Sie eine optionale Beschreibung hier ein WARNING: Delete list? WARNUNG: Liste löschen? &Yes, delete it &Ja, löschen &No &Nein LogViewer Log Log-Datei Clear &Log Log-Datei &leeren &Close &Schließen MainWindow Meanwhile... Inzwischen... Side &Panel Seiten&leiste Status &Bar &Statusleiste Total posts: %1 Beiträge gesamt: %1 &Timeline Zeitleis&te The main timeline Die Haupt-Zeitleiste &Activity &Aktivität Your own posts Ihre eigenen Beiträge Your favorited posts Ihre favorisierten Beiträge &Messages &Nachrichten Messages sent explicitly to you Lit. "Messages for you", a literal translation would be too long Nachrichten direkt an Sie &Contacts &Kontakte Initializing... Starten... Your account is not configured yet. Ihre Konto ist noch nicht konfiguriert. Dianara started. Dianara gestartet. Minor activities done by everyone, such as replying to posts *** Note by Jan: This shouldn't need a line break, since the tooltip displaying this message will use word wrapping for any language. But it might be a matter of style, I don't know ;) Neben-Aktivitäten von allen, z.B. Antworten auf Beiträge Minor activities addressed to you An Sie gerichtete Neben-Aktivitäten Actions Aktionen Minor activities done by you Neben-Aktivitäten von Ihnen The people you follow, the ones who follow you, and your person lists Die Leute denen Sie folgen, diejenigen, die Ihnen folgen, und Ihre Kontaklisten Running with Qt v%1. &Session &Sitzung Update Minor &Feed Neben&feed aktualisieren Update Mentions Erwähnungen aktualisieren Update Actions Aktionen aktualisieren Auto-update &Timelines Zei&tleisten automatisch aktualisieren Mark All as Read Alles als 'gelesen' markieren &Post a Note &Mitteilung veröffentlichen &Quit &Verlassen &View &Ansicht &Toolbar Werkzeugleis&te Full &Screen Voll&bild &Log &Log-Datei S&ettings &Einstellungen Edit &Profile &Profil bearbeiten &Account &Konto Basic &Help &Hilfe zu Grundlagen Report a &Bug Einen &Fehler melden Pump.io User &Guide Pump.io &Leitfaden Some Pump.io &Tips Einige &Tipps zu Pump.io List of Some Pump.io &Users Liste einiger P&ump.io-Benutzer Pump.io &Network Status Website Toolbar Werkzeugleiste Open the log viewer Log-Betrachter öffnen Auto-updating enabled Automatisches Aktualisieren aktiv Auto-updating disabled Automatisches Aktualisieren inaktiv Proxy password required Proxy-Passwort benötigt You have configured a proxy server with authentication, but the password is not set. Sie haben einen Proxy-Server mit Authentifizierung konfiguriert, aber das Passwort ist nicht eingestellt. Enter the password for your proxy server: Geben Sie das Passwort für Ihren Proxy-Server ein: Starting automatic update of timelines, once every %1 minutes. Beginne automatische Aktualisierung der Zeitleisten alle %1 Minuten. Stopping automatic update of timelines. Beende automatische Aktualisierung der Zeitleisten. Received %1 older posts in '%2'. %1 is a number, %2 = name of a timeline 1 more pending to receive. singular, one post %1 more pending to receive. plural, several posts Also: Last update: %1 '%1' updated. %1 is the name of a feed (Jan) tried my luck '%1' aktualisiert. 1 filtered out. singular, refers to a post %1 filtered out. plural, refers to posts 1 deleted. singular, refers to a post %1 deleted. plural, refers to posts Mentions Erwähnungen There is 1 new activity. Es gibt 1 neue Aktivität. There are %1 new activities. Es gibt %1 neue Aktivitäten. 1 highlighted. singular, refers to an activity refers to "Aktivität" 1 hervorgehobene. %1 highlighted. plural, refers to activities refers to "Aktivitäten" %1 hervorgehobene. 1 filtered out. singular, refers to one activity %1 filtered out. plural, several activities No new activities. Keine neuen Aktivitäten. Error storing image! %1 bytes Marking everything as read... Closing due to environment shutting down... Quit? Verlassen? You are composing a note or a comment. Sie schreiben gerade eine Mitteilung oder einen Kommentar. Do you really want to close Dianara? Wollen Sie Dianara wirklich schließen? &Yes, close the program &Ja, Programm schließen &No &Nein Shutting down Dianara... System tray icon is not available. Dianara cannot be hidden in the system tray. Do you want to close the program completely? Timeline updated at %1. Zeitleiste aktualisiert um %1. Press F1 for help Click here to configure your account Update %1 Show Welcome Wizard Your Pump.io account is not configured Ihr Pump.io-Konto ist nicht konfiguriert Received %1 older activities in '%2'. %1 is a number, %2 = name of feed 1 more pending to receive. singular, 1 activity %1 more pending to receive. plural, several activities Link to: %1 Link zu: %1 With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. Mit Dianara können Sie Ihre Zeitleisten betrachten, neue Mitteilungen schreiben, Bilder und andere Medien hochladen, mit Beiträgen interagieren, Ihre Kontakte verwalten und neuen Leuten folgen. English translation by JanKusanagi. TRANSLATORS: Change this with your language and name. If there was another translator before you, add your name after theirs ;) Deutsche Übersetzung durch Eugenio M. Vigo und mightyscoopa. Dianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) Dianara ist unter der GNU-GPL-Lizenz lizenziert, und verwendet einige Symbole von Oxygen: http://www.oxygen-icons.org/ (LGPL lizenziert) &Configure Dianara Dianara &einrichten &Update Timeline Zeitleiste akt&ualisieren Update &Messages &Nachrichten aktualisieren Update &Activity '&Aktivität' aktualisieren Update Fa&vorites Fa&voriten aktualisieren &Filters and Highlighting &Filter und Hervorheben &Help &Hilfe Visit &Website &Webseite besuchen About &Dianara Über &Dianara Your biography is empty Ihre Biographie ist leer Click to edit your profile Klicken Sie um Ihr Profil zu bearbeiten 1 highlighted. singular, refers to a post refers to "Beitrag" 1 hervorgehoben. %1 highlighted. plural, refers to posts refers to "Beiträge" %1 hervorgehobene. No new posts. Keine neuen Beiträge. Favor&ites Favor&iten Minor feed updated at %1. Nebenfeed aktualisiert um %1. Minor feed updated. Nebenfeed aktualisiert. &Hide Window Fenster &verstecken &Show Window Fen&ster zeigen There is 1 new post. Es gibt 1 neuen Beitrag. There are %1 new posts. Es gibt %1 neue Beiträge. Timeline updated. Zeitleiste aktualisiert. About Dianara Über Dianara Dianara is a pump.io social networking client. Dianara ist ein Client für das soziale Netzwerk Pump.io. Thanks to all the testers, translators and packagers, who help make Dianara better! Gedankt sei allen Testern, Übersetzern und Paketerstellern, die dabei helfen, Dianara zu verbessern! MinorFeed Older Activities Ältere Aktivitäten Get previous minor activities Vorherige Nebenaktivitäten abrufen There are no activities to show yet. Es gibt noch keine Aktivitäten zu zeigen. Get %1 newer As in: Get 3 newer (activities) MinorFeedItem Using %1 Application used to generate this activity Mit %1 To: %1 1=people to whom this activity was sent An: %1 Cc: %1 1=people to whom this activity was sent as CC Cc: %1 CC: %1 1=people to whom this activity was sent as CC CC: %1 Open referenced post Erwähnten Beitrag öffnen MiscHelpers bytes Bytes PageSelector Jump to page Zu Seite springen Page number: Seitenzahl: &First As in: first page &Last As in: last page Newer As in: newer pages Neuer Older As in: older pages Älter Newer Neuer Older Älter &Go &Los Go to &last page &Gehe zur letzten Seite &Cancel &Abbrechen PeopleWidget &Search: &Suche: Enter a name here to search for it Geben Sie hier einen Namen ein, um danach zu suchen Add a contact to a list Einen Kontakt zu einer Liste hinzufügen &Cancel &Abbrechen Post Like Gefällt mir Like this post Diese Nachricht favorisieren 1 like 1x favorisiert 1 comment 1 Kommentar Shared %1 times %1x geteilt Shared on %1 Geteilt am %1 Edited: %1 Bearbeitet: %1 In In Using %1 1=Program used for posting or sharing Mit %1 Share Teilen Edit Bearbeiten Loading image... Lade Bild... Attached file Angehängte Datei %1 likes %1x favorisiert %1 comments %1 Kommentare Delete Löschen Post Noun, not verb Beitrag Via %1 Via %1 Posted on %1 1=Date Am %1 veröffentlicht To An CC CC Parent As in 'Open the parent post'. Try to use the shortest word! Vorläufer Open the parent post, to which this one replies Öffne Vorläuferbeitrag, auf den dieser antwortet If you select some text, it will be quoted. Lit. "Selected text will be quoted" Ausgewählter Text wird zitiert. Unshare Nicht mehr teilen Unshare this post Diesen Beitrag nicht mehr teilen Open post in web browser Diesen Beitrag im Browser öffnen Click to download the attachment Klicken, um den Anhang herunterzuladen Cc Cc Copy post link to clipboard Kopiere den Beitragslink in die Zwischenablage Normalize text colors Normalisiere Schriftfarben &Close S&chließen Type As in: type of object Typ Modified on %1 Am %1 modifiziert Comment verb, for the comment button Kommentieren Reply to this post. Auf diesen Beitrag antworten. Share this post with your contacts Diesen Beitrag mit Ihren Kontakten teilen Modify this post Diesen Beitrag bearbeiten Erase this post Diesen Beitrag löschen Join Group Gruppe beitreten %1 members in the group %1 Mitglieder in der Gruppe Image is animated. Click on it to play. Bild ist animiert. Darauf klicken um es abzuspielen. Couldn't load image! Attached Audio Angehängte Audiodatei Attached Video Angehängte Videodatei Attached File Angehängte Datei %1 likes this One person %1 gefällt dies %1 like this More than one person German uses: it +like+dative, so the verb remains in the singular (similar to Spanish) %1 gefällt dies %1 shared this %1 = One person name %1 hat dies geteilt %1 shared this %1 = Names for more than one person %1 haben dies geteilt Shared once 1x geteilt You like this Du hast dies favorisiert Unlike Gefällt mir nicht Share post? Beitrag teilen? Do you want to share %1's post? Wollen Sie %1s Beitrag teilen? &Yes, share it &Ja, teilen &No &Nein Unshare post? Beitrag nicht mehr teilen? Do you want to unshare %1's post? Wollen Sie das Teilen von %1s Beitrag rückgängig machen? &Yes, unshare it &Ja, nicht mehr teilen WARNING: Delete post? WARNUNG: Beitrag löschen? Are you sure you want to delete this post? Lit.: "Are you sure that you want to delete this post?" Sind Sie sicher, dass Sie diesen Beitrag löschen wollen? &Yes, delete it &Ja, löschen Click the image to see it in full size Klicken Sie auf das Bild, um es in voller Größe zu sehen ProfileEditor Profile Editor Profil-Editor This is your Pump address Dies ist Ihre Pump.io-Adresse This is the e-mail address associated with your account, for things such as notifications and password recovery Dies ist die mit Ihrem Konto verknüpfte E-Mail-Adresse, um Benachrichtigungen zu erhalten und für die Passwortwiederherstellung Change &E-mail... Change &Avatar... &Avatar ändern... This is your visible name Dies ist Ihr sichtbarer Name &Save Profile Profil &speichern &Cancel &Abbrechen Webfinger ID Webfinger-ID E-mail E-Mail Avatar Avatar Full &Name Ganzer &Name &Hometown &Heimatort &Bio &Biographie Not set In reference to the e-mail not being set for the account Nicht eingestellt Select avatar image Bild für Avatar auswählen Image files Bilddateien All files Alle Dateien Invalid image Ungültige Bilddatei The selected image is not valid. Die ausgewählte Bilddatei ist nicht gültig. ProxyDialog Proxy Configuration Proxy-Konfiguration Do not use a proxy Keinen Proxy verwenden Your proxy username Ihr Proxy-Benutzername Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. Hinweis: Das Passwort wird nicht sicher hinterlegt. Wenn Sie wollen, können Sie das Feld leer lassen, dann werden Sie beim Start nach dem Passwort gefragt. &Save &Speichern &Cancel &Abbrechen Proxy &Type Proxy-&Typ &Hostname &Host-Name &Port &Port Use &Authentication &Authentifizierung verwenden &User Ben&utzer Pass&word Pass&wort Publisher Title Titel Select Picture... Bild auswählen... Find the picture in your folders Bild in Ihren Ordnern finden Public Öffentlich Followers Anhängerschaft Lists Listen To... An... CC... CC... Select who will get a copy of this post Auswählen, wer eine Kopie dieses Beitrags erhalten soll Other as in other kinds of files Sonstiges Cancel Abbrechen Cancel the post "Discard post" Beitrag verwerfen Picture not set "Picture not chosen" Bild nicht gewählt Select Audio File... Audiodatei auswählen... Find the audio file in your folders Audiodatei in Ihren Ordnern finden Audio file not set Audiodatei nicht gewählt Select Video... Video auswählen... Find the video in your folders Video in Ihren Ordnern finden Video not set Video nicht gewählt Select File... Datei auswählen... Find the file in your folders Datei in Ihren Ordnern finden File not set Datei nicht gewählt Error: Already composing Fehler: Bereits am Erstellen You can't edit a post at this time, because a post is already being composed. Sie können jetzt keinen Beitrag bearbeiten, weil bereits ein anderer Beitrag erstellt wird. Update Aktualisieren Editing post "Edit post" Bearbeite Beitrag You can't create a message for %1 at this time, because a post is already being composed. Sie können jetzt keine Nachricht an %1 erstellen, weil bereits ein anderer Beitrag erstellt wird. Posting failed. Try again. Veröffentlichung fehlgeschlagen. Erneut versuchen. Warning: You have no followers yet Warnung: Sie haben noch keine Anhängerschaft You're trying to post to your followers only, but you don't have any followers yet. Sie versuchen nur an Ihre Anhängerschaft zu veröffentlichen, aber Sie haben noch keine. If you post like this, no one will be able to see your message. Wenn Sie so veröffentlichen, wird niemand Ihren Beitrag sehen können. &Cancel, go back to the post &Abbrechen, zurück zum Beitrag Updating... Aktualisiere... Post is empty. Der Beitrag ist leer. File not selected. Datei nicht ausgewählt. Select one image Wählen Sie ein Bild aus Image files Bilddateien Select one file Wählen Sie eine Datei aus Invalid file Ungültige Datei The file type cannot be detected. "(...) is not recognized." Der Dateityp wird nicht erkannt. All files Alle Dateien Since you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. Da Sie ein Bild hochladen wollen, könnten Sie es ein wenig verkleinern oder es in einem komprimierten Format wie JPG speichern. File is too big Die Datei ist zu groß Dianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. Dianara begrenzt das Hochladen von Dateien z.Zt. auf 10 MiB pro Beitrag, um mögliche Speicherplatz- oder Netzwerkprobleme der Server zu vermeiden. This is a temporary measure, since the servers cannot set their own limits yet. Dies ist eine vorläufige Maßnahme, da die Server noch nicht ihre eigenen Begrenzungen aufstellen können. Sorry for the inconvenience. Entschuldigen Sie die Unannehmlichkeiten. Resolution Auflösung Type Typ Size Größe %1 KiB of %2 KiB uploaded %1 KiB von %2 KiB hochgeladen Invalid image Ungültige Bilddatei Setting a title helps make the Meanwhile feed more informative Einen Titel einzustellen hilft dabei die 'Inzwischen'-Zeitleiste informativer zu machen Add a brief title for the post (recommended) Fügen Sie dem Beitrag einen kurzen Titel hinzu (empfohlen) Remove Cancel the attachment, and go back to a regular note Cc... Cc... Post verb Veröffentlichen Note started from another application. Ignoring new note request from another application. Do you want to make the post public instead of followers-only? Wollen Sie den Beitrag öffentlich, anstatt nur für Ihre 'Anhängerschaft' machen? &Yes, make it public &Ja, öffentlich machen &No, post to my followers only &Nein, nur an 'Anhängerschaft' veröffentlichen The image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. Das Format der Bilddatei wird nicht erkannt. Vielleicht ist deren Erweiterung falsch, bspw. eine GIF-Datei die nach 'image.jpg' umbenannt wurde o.ä. Select one audio file Wählen Sie eine Audiodatei aus Audio files Audiodateien Invalid audio file Ungültige Audiodatei The audio format cannot be detected. Das Format der Audiodatei wird nicht erkannt. Select one video file Wählen Sie _eine_ Videodatei aus Video files Videodateien Invalid video file Ungültige Videodatei The video format cannot be detected. Das Format der Videodatei wird nicht erkannt. Posting... Veröffentliche... Add a brief title for the post here (recommended) (Jan) This one needs to be updated with the "here" Fügen Sie dem Beitrag einen kurzen Titel hinzu (empfohlen) People... Leute... Select who will see this post Auswählen, wer diesen Beitrags sehen soll Picture Bild Audio Audio Video Video Ad&d... Hin&zufügen... Upload media, like pictures or videos Medien, wie Bilder oder Videos, hochladen Hit Control+Enter to post with the keyboard Drücken Sie Strg+Eingabe, um mit der Tastatut zu veröffentlichen PumpController Creating person list... Erstelle Personenliste... Deleting person list... Lösche Personenliste... Getting a person list... Rufe eine Personenliste ab... Adding person to list... Füge Person zu Liste hinzu... Removing person from list... Entferne Person von Liste... Creating group... Erstelle Gruppe... Joining group... Trete Gruppe bei... Leaving group... Verlasse Gruppe... Main timeline update requested, but updates are blocked. Aktualisierung der Haupt-Zeitleiste angefordert, aber Aktualisierungen sind blockiert. Direct timeline update requested, but updates are blocked. Aktualisierung der Direktnachrichten angefordert, aber Aktualisierungen sind blockiert. Getting direct messages timeline... Rufe Direktnachrichten ab... Activity timeline update requested, but updates are blocked. left out "timeline" for shortness Aktualisierung von 'Aktivität' angefordert, aber Aktualisierungen sind blockiert. Favorites timeline update requested, but updates are blocked. Aktualisierung der Favoriten angefordert, aber Aktualisierungen sind blockiert. Getting favorites timeline... Rufe Favoriten ab... Timeline update requested, but updates are blocked. Aktualisierung der Zeitleiste angefordert, aber Aktualisierungen sind blockiert. Getting timeline... Rufe Zeitleiste ab... Getting likes... Rufe Favorisierungen ab... The comments for this post cannot be loaded due to missing data on the server. Getting comments... Rufe Kommentare ab... Getting minor feed... Rufe Nebenfeed ab... Activity Aktivität Favorites Favoriten Meanwhile Inzwischen Mentions Erwähnungen Actions Aktionen Getting '%1'... %1 is the name of a feed Timeline Zeitleiste Messages Nachrichten Uploading %1 1=filename Lade %1 hoch HTTP error For the following HTTP error codesyou can check http://en.wikipedia.org/wiki/List_of_HTTP_status_codes in your language HTTP-Fehler Gateway Timeout HTTP 504 error string Gateway-Zeitüberschreitung Service Unavailable HTTP 503 error string Dienst nicht verfügbar Not Implemented HTTP 501 error string *** this was added temporarily by Jan, due to last minute addition of the string, might be wrong Nicht implementiert Internal Server Error HTTP 500 error string Interner Serverfehler Gone HTTP 410 error string Verschwunden Not Found HTTP 404 error string Nicht gefunden Forbidden HTTP 403 error string Verboten Unauthorized HTTP 401 error string Nicht autorisiert Bad Request HTTP 400 error string Falsche Anfrage Moved Temporarily HTTP 302 error string Temporär verschoben Moved Permanently HTTP 301 error string Permanent verschoben Error connecting to %1 Fehler beim verbinden mit %1 Unhandled HTTP error code %1 Unbehandelter HTTP-Fehler-Code %1 Profile received. Profil empfangen. Followers Anhängerschaft Following Verfolgte Profile updated. Profil aktualisiert. Post published successfully. Beitrag erfolgreich veröffentlicht. Avatar published successfully. Avatar erfolgreich veröffentlicht. Post updated successfully. Beitrag erfolgreich aktualisiert. Comment updated successfully. Kommentar erfolgreich aktualisiert. Comment posted successfully. Kommentar erfolgreich veröffentlicht. 1 comment received. 1 Kommentar empfangen. %1 comments received. %1 Kommentare empfangen. Post by %1 shared successfully. 1=author of the post we are sharing Beitrag von %1 erfolgreich geteilt. Minor feed received. Nebenfeed empfangen. Following %1 (%2) successfully. %1 is a person's name, %2 is the ID Folgen von %1 (%2) erfolgreich. Stopped following %1 (%2) successfully. %1 is a person's name, %2 is the ID Folgen von %1 (%2) erfolgreich beendet. List of 'following' completely received. Liste 'Verfolgte' vollständig empfangen. Partial list of 'following' received. Liste 'Verfolgte' teilweise empfangen. List of 'followers' completely received. Liste 'Anhängerschaft' vollständig empfangen. Partial list of 'followers' received. Liste 'Anhängerschaft' teilweise empfangen. Person list deleted successfully. Personenliste erfolgreich gelöscht. Person list received. Personenliste empfangen. File downloaded successfully. Datei erfolgreich heruntergeladen. File uploaded successfully. Posting message... Datei erfolgreich hochgeladen. Veröffentliche Beitrag... Loading external image from %1 regardless of SSL errors, as configured... %1 is a hostname OAuth error while authorizing application. OAuth-Fehler während des Autorisierens der Anwendung. Timeline received. Updating post list... Zeitleiste empfangen. Aktualisiere Liste der Beiträge... Authorized to use account %1. Getting initial data. Autorisiert für Konto %1. Rufe einleitende Daten ab. There is no authorized account. Es gibt kein autorisiertes Konto. Getting list of 'Following'... Rufe Liste 'Verfolgte' ab... Getting list of 'Followers'... Rufe Liste 'Anhängerschaft' ab... Getting list of person lists... Rufe Liste der Personenlisten ab... Getting main timeline... Rufe Haupt-Zeitleiste ab... Getting activity timeline... Rufe 'Aktivität' ab... Message liked or unliked successfully. Beitrag erfolgreich (de)favorisiert. Likes received. Favorisierungen empfangen. %1 published successfully. Updating post content... %1 is the type of object: note, image... Getting site users for %1... %1 is a server name User timeline E-mail updated: %1 Untitled post %1 published successfully. %1 is a piece of the post Post %1 published successfully. %1 is the title of the post Untitled post %1 updated successfully. %1 is a piece of the post Post %1 updated successfully. %1 is the title of the post Comment %1 updated successfully. %1 is a piece of the comment Comment %1 posted successfully. %1 is a piece of the comment Received '%1'. %1 is the name of a feed Adding items... Message deleted successfully. Beiträge erfolgreich gelöscht. List of 'lists' received. Liste der 'Listen' erhalten. List of %1 users received. %1 is a server name Person list '%1' created successfully. Personenliste '%1' erfolgreich erstellt. %1 (%2) added to list successfully. 1=contact name, 2=contact ID %1 (%2) erfolgreich zu Liste hinzugefügt. %1 (%2) removed from list successfully. 1=contact name, 2=contact ID %1 (%2) erfolgreich von Liste entfernt. Group %1 created successfully. Gruppe %1 erfolgreich erstellt. Group %1 joined successfully. Gruppe %1 erfolgreich beigetreten. Left the %1 group successfully. Gruppe %1 erfolgreich verlassen. Avatar uploaded. Avatar hochgeladen. SSL errors in connection to %1! The application is not registered with your server yet. Registering... Die Anwendung ist noch nicht bei Ihrem Server registriert. Registriere... Getting OAuth token... Rufe Oauth-Token ab... OAuth support error OAuth-Support-Fehler Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support. Ihre Installation von QOAuth, einer von Dianara verwendeten Bibliothek, scheint HMAC-SHA1 nicht zu unterstützen. You probably need to install the OpenSSL plugin for QCA: %1, %2 or similar. Sie müssen wahrscheinlich das OpenSSL-Plugin für QCA installieren: %1, %2 o.ä. Authorization error Autorisierungsfehler There was an OAuth error while trying to get the authorization token. Ein OAuth-Fehler trat auf, während versucht wurde das Autorisierungs-Token abzurufen. QOAuth error %1 QOAuth-Fehler %1 Application authorized successfully. Anwendung erfolgreich autorisiert. Waiting for proxy password... Warte auf Proxy-Passwort... Still waiting for profile. Trying again... Warte weiterhin auf Profil. Versuche es erneut... %1 attempts 1 attempt Some initial data was not received. Restarting initialization... Einige einleitenden Daten wurden nicht empfangen. Beginne erneute Initialisierung... Some initial data was not received. Restarting initialization. Einige einleitenden Daten wurden nicht empfangen. Beginne erneute Initialisierung. Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally. Einige einleitenden Daten wurden nach mehreren Versuchen nicht empfangen. Vielleicht stimmt etwas mit Ihrem Server nicht. Dennoch ist es möglich, dass Sie den Dienst normal verwenden können. All initial data received. Initialization complete. Alle einleitenden Daten empfangen. Initialisierung abgeschlossen. Ready. Bereit. SiteUsersList You can get a list of the newest users registered on your server by clicking the button below. More resources to find users: Wiki page 'Users by language' PPump user search service at inventati.org Get list of users from your server Close list Loading... %1 users in %2 %1 = user count, %2 = server name TimeLine Welcome to Dianara Willkommen bei Dianara Dianara is a <b>pump.io</b> client. Dianara ist ein <b>Pump.io</b>-Client. Dianara is a <b>Pump.io</b> client. Dianara ist ein <b>Pump.io</b>-Client. If you don't have a Pump account yet, you can get one at the following address, for instance: Falls Sie noch kein Pump.io-Konto haben, können Sie z.B. bei folgenden Adressen eines bekommen: Press <b>F1</b> if you want to open the Help window. Drücken Sie <b>F1</b>, falls Sie das Hilfe-Fenster öffnen wollen. First, configure your account from the <b>Settings - Account</b> menu. Als Erstes sollten Sie Ihr Konto über das Menü <b>Einstellungen - Konto</b> einrichten. After the process is done, your profile and timelines should update automatically. Nachdem der Vorgang abgeschlossen ist, sollten sich Ihr Profil und Ihre Zeitleisten automatisch aktualisieren. Take a moment to look around the menus and the Configuration window. Nehmen sich einen Moment, um sich in den Menüs und im Konfigurationsfenster umzuschauen. You can also set your profile data and picture from the <b>Settings - Edit Profile</b> menu. Sie können auch Profilinformationen und -bild über das Menü <b>Einstellungen - Profil bearbeiten</b> einstellen. There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information. Es gibt überall Tooltips, sodass Sie beim Überfahren einer Schaltfläche oder eines Eingabefeldes wahrscheinlich zusätzliche Informationen sehen. Dianara's blog Dianaras Blog Pump.io User Guide Pump.io-Leitfaden Direct Messages Timeline Direktnachrichten-Zeitleiste Here, you'll see posts specifically directed to you. Hier werden Sie speziell an Sie gerichtete Beiträge sehen. Activity Timeline Aktivität-Zeitleiste You'll see your own posts here. Hier werden Sie Ihre eigenen Beiträge sehen. Favorites Timeline Favoriten Zeitleiste Posts and comments you've liked. Beiträge und Kommentare, die Sie favorisiert haben. Newest Neueste Newer Neuer Older Älter Requesting... Loading... Page %1 of %2. Seite %1 von %2. Showing %1 posts per page. Zeige %1 Beiträge pro Seite. %1 posts in total. %1 Beiträge insgesamt. Click here or press Control+G to jump to a specific page Klicken Sie hier oder drücken Sie Str+G, um zu einer bestimmten Seite zu springen '%1' cannot be updated because a comment is currently being composed. %1 = feed's name %1 more posts pending for next update. Click here to receive them now. There are no posts Timestamp Invalid timestamp! Ungültiger Zeitstempel! A minute ago Vor einer Minute %1 minutes ago Vor %1 Minuten An hour ago Vor einer Stunde %1 hours ago Vor %1 Stunden Just now Gerade jetzt In the future In der Zukunft Yesterday Gestern %1 days ago Vor %1 Tagen A month ago Vor einem Monat %1 months ago Vor %1 Monaten A year ago Vor einem Jahr %1 years ago Vor %1 Jahren UserPosts Posts by %1 Loading... &Close &Schließen Received '%1'. %1 posts Error loading the timeline dianara-v1.3.2/translations/dianara_de.qm000644 000764 000764 00000304636 12614520620 020035 0ustar00janjan000000 000000 ""T(k*E*0]+v++3+l+,}2>c <͚<+Z@)DCBjH1HHIaKMe@NANP7P7`RS  TETfV*g)VV!*WjHOWLRXXƥYZy%29[ %03[!0`\\1j knwjXyT5z>H3 hLyN"cH>lGl87.P"42̸%ՅB I5 wҥH~[*? $eNBf>[_ FdݣsW$Y1-!*01<# <GDInIlM$P3u#QE.RS`V8x\qR~lop`NW$=#,-2rPh>QE^Jt@$wbΣ%i 2L  $ UN%%MKLRxNhBF]pH2itɥ v0w(4SdnJgUS^_|Ɏjtx6t6p6ȇ6!6rA! `؈ nMJ!oƭ>I?Z4hoϠN%؞~d^@ު8Y]3@#jqJ<   c vS$1$1^(Jd.sig0cG0ct 66@N6'@70?K@>5M.)>Zadi6c^il#9sCwx2_zbn}\Tz+.QϻNn=.^_"B^bje¿S¿Ì P^qq޷?ds_b)ž4nÑ% ?'ʳX 1HD#n6U݄>naB&EP `3a$gUJIr) Z:C:Y<7+<́ U>]=I!AIg"Ir6kJt N6>#,b,u<3ԁ[^W&cMsEmB4ͣ}3g3gZրyvRlo@kz+7W#;FFd /d<#Z,C04DKTKT]ZLܤfRM=R=V|0]L.h]DIa*bcѸfy ;h9~6hTsnfu_Qy{9{#|7}D%l,. {RVVYh>,!4L>vQT#tAwGh=:4>úX`\wa`0 0ƨ02uonSf5pg]NJRأH "*]+-^J4uCD4u4u R9$#<.<.<.[%=C>VI׌M?$LVх\ZPʽZztSZxf;/ghCmnG[p{,r7!_uy a/q$ Nn S,1q̐%VO&4\ґZڑsڱnqm1h*!iCxH#D0;1;1<C<<<scgQjhlqNaprˍt${km=jo&U8ROtfxҕX>ACN:]i QHi=9n77E`"!#N )2K1.2?*Fȿ\]JNece7ds#$sn+|2XV|<H4UAT'pOۛi?erB>PS/q8`0rnť(]琊*ZR\S !{ -Qn 0\~< 0u~ 1`#: 6  7w/ ^ni r%S~ w>sFA |Dž ~2 t 4 2=  : `o| o bn ʞ UB p.Z &Q ~I Ԭ [< hC2 NW@ Aq AH  ~_ op  !/? #D (OG ?m Hk[ TuS TG] TG [de ] c d 4L d< d<!| d< pO. q\+< v'V z3.c 4_ F I# Ih I:z Ik Il3 I IT I I ) ~Y IrP 8 * y N7 o! 0 t?V Ku ^g '_ t:: Ձ@ 3G` ֓(} N %%W T ^7 ؄ , 0 :" n, Yf >m #I c2 O F "e "i ,i4( 0 5<X <خj @ ED Ya% bX"r bX' eT f( f*2 mmT o> |6" P/p !>Z S> #LD M VN a2 F k )M R# 7 sd j` أ y 9 C=@ 24 (=y - U 64k BM2 E YB [cJ ]ӡl `ucg {Q { 7 $ E rwr V xK [; q= #f s ͡ =&% Б*w F <b yf| ; w0 a$ =% g Е^ #pn, .> G ^ Nn\ V)! _P1: b e&I h1[ iFC> @ |. >oT ^T q+ ˽+.c ~ b r s  ` ґV"3N2To6+j?/_R/,gRT3.Xc*gO<lNMllQp'_a'5QU~|gA%&}83&U;O+y]RO #ue`^j_^Xkz67'5%'5%'5%i'EN*)p+<ε,02A KKBtnOB}~7{C\ FJE<PdwrT;_}-eY.R`i,2^'C/~Vn?_6.~<^C؞m¹e5H{~mdzGD"Iڨd!ޝB[ir%1 von %2%1 by %2 ASActivityffentlichPublic ASActivityArtikelArticleASObject AudioAudioASObjectSammlung CollectionASObjectKommentarCommentASObjectGelscht am %1 Deleted on %1ASObject DateiFileASObject GruppeGroupASObjectBildImageASObject kein genauer OrtNo detailed locationASObjectMitteilungNoteASObjectSonstigesOtherASObject VideoVideoASObjectund %1 weitere and %1 othersASObject*und ein(e) Weitere(r) and one otherASObjectHeimatortHometownASPerson.Anwendung &autorisieren&Authorize Application AccountDialog&Abbrechen&Cancel AccountDialog Daten &speichern &Save Details AccountDialogEin Browser wird jetzt gestartet, um den Verifizierungskode zu erhaltenAA web browser will start now, where you can get the verifier code AccountDialog&Konto konfigurierenAccount Configuration AccountDialogNachdem Sie diese Schaltflche gedrckt haben, wird sich ein Webbrowser ffnen, um die Autorisierung fr Dianara anzufordernYAfter clicking this button, a web browser will open, requesting authorization for Dianara AccountDialog\Dianara hat autorisierten Zugang zu Ihre Daten)Dianara is authorized to access your data AccountDialogGeben Sie hier den von Ihrem Pump-Server bereitgestellten Verifizierungskode einBEnter or paste the verifier code provided by your Pump server here AccountDialog|Geben Sie zuerst Ihre Webfinger ID (Ihre Pump.io Adresse) ein.5First, enter your Webfinger ID, your pump.io address. AccountDialog8&Verifizierungskode erhaltenGet &Verifier Code AccountDialogFalls der Browser sich nicht automatisch ffnet, kopieren Sie die folgende Adresse von HandEIf the browser doesn't open automatically, copy this address manually AccountDialogFr ein Profil auf 'https://pump.beispiel/benutzer', ist Ihre zugehrige Adresse 'benutzer@pump.beispiel'_If your profile is at https://pump.example/yourname, then your address is yourname@pump.example AccountDialogSobald Sie Dianara von der Oberflche Ihres Pump-Servers aus autorisiert haben, werden Sie einen Code namens 'VERIFIER' erhalten. Kopieren Sie diesen und fgen Sie ihn in das Feld unterhalb ein.Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. AccountDialog"Pump.io LeitfadenPump.io User Guide AccountDialog6Verifizierungskode ist leerVerifier code is empty AccountDialog&Verifizierungskode:Verifier code: AccountDialog<Ihre Pump-Adresse ist ungltigYour Pump address is invalid AccountDialog*Ihre Pump.io Adresse:Your Pump.io address: AccountDialogIhre Adresse sieht aus wie 'Benutzer@pumpeserver.org'. Sie knnen diese ber die Web-Oberflche in Ihrem Profil finden.kYour address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. AccountDialogBIhre Adresse, als benutzer@server*Your address, like username@pumpserver.org AccountDialog.Zur Auswahl &hinzufgen&Add to SelectedAudienceSelector&Abbrechen&CancelAudienceSelector&Fertig&DoneAudienceSelector$Cc-Empfnger Liste 'Cc' ListAudienceSelectorEmpfnger Liste 'To' ListAudienceSelectorAlle Kontakte All ContactsAudienceSelector&Liste leeren Clear &ListAudienceSelectorTWhlen Sie Kontakte aus der Liste links aus. Sie knnen diese mit der Maus ziehen, einfach oder doppelt anklicken oder sie auswhlen und die Schaltflche unten verwenden.:ON THE LEFT should change to ON THE RIGHT in RTL languagesSelect people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below.AudienceSelector(Ausgewhlte KontakteSelected PeopleAudienceSelector &Nein&No AvatarButton,&Ja, nicht mehr folgen&Yes, stop following AvatarButtonlSind Sie sicher, dass Sie %1 nicht mehr folgen wollen?+Are you sure you want to stop following %1? AvatarButton FolgenFollow AvatarButton>Profil von %1 im Browser ffnen Open %1's profile in web browser AvatarButton<Ihres Profil im Browser ffnen Open your profile in web browser AvatarButton4Sende eine Nachricht an %1Send message to %1 AvatarButton"Nicht mehr folgenStop following AvatarButton$Nicht mehr folgen?Stop following? AvatarButtonWechselnChange ColorPicker6%1 gefllt dieser Kommentar%1 like this commentComment6%1 gefllt dieser Kommentar%1 likes this commentComment &Nein&NoComment&Ja, lschen&Yes, delete itCommenttSind Sie sicher, dass Sie diesen Kommentar lschen wollen?-Are you sure you want to delete this comment?CommentLschenDeleteCommentBearbeitenEditComment0Diesen Kommentar lschenErase this commentCommentFavorisierenLikeComment\Diesen Kommentar favorisieren / defavorisierenLike or unlike this commentCommentAm %1 verndertModified on %1Comment6Diesen Kommentar bearbeitenModify this commentComment(Am %1 verffentlicht Posted on %1CommentZitierenQuoteCommentFAntwort mit Zitat dieses KommentarsReply quoting this commentCommentDefavorisierenUnlikeComment6WARNUNG: Kommentar lschen?WARNING: Delete comment?CommentAbbrechenCancelCommenterBlockKommentierenCommentCommenterBlock&Kommentar ist leer.Comment is empty.CommenterBlock(Kommentar bearbeitenEditing commentCommenterBlock:Fehler: Schon am KommentierenError: Already composingCommenterBlockVerffentlichung des Kommentars schlug fehl. Versuchen Sie es noch einmal.#Posting comment failed. Try again.CommenterBlockDrcken Sie ESC um den Kommentar abzubrechen, wenn das Textfeld leer ist3Press ESC to cancel the comment if there is no textCommenterBlock(Kommentare neu ladenReload commentsCommenterBlock4Kommentar wird gesendet...Sending comment...CommenterBlock0Zeige alle %1 KommentareShow all %1 commentsCommenterBlock<Kommentar wird aktualisiert...Updating comment...CommenterBlockSie knnen Strg+Eingabe auf der Tastatur drcken, um den Kommentar abzusendenAYou can press Control+Enter to send the comment with the keyboardCommenterBlockSie knnen jetzt keinen Kommentar bearbeiten, weil bereits ein anderer Kommentar erstellt wird.YYou can't edit a comment at this time, because another comment is already being composed.CommenterBlock&Format&FormatComposer &Nein&NoComposer&Ja, abbrechen&Yes, cancel itComposertSind Sie sicher, dass Sie diesen Beitrag verwerfen wollen?-Are you sure you want to cancel this message?ComposerFettBoldComposer&Beitrags verwerfen?Cancel message?ComposerKlicken Sie hier oder drcken Sie Strg+N um eine Mitteilung zu schreiben.../Click here or press Control+N to post a note...Composer*Fehler: Ungltige URLError: Invalid URLComposer Format FormattingComposerVorspannHeaderComposer6Wie viele Spalten (Breite)?How many columns (width)?Composer0Wie viele Zeilen (Hhe)?How many rows (height)?ComposerLink hinzufgen Insert a linkComposerHFgen Sie ein Bild aus einer URL einInsert an image from a URLComposerDBild von einer Webseite hinzufgenInsert an image from a web siteComposer$Als Bild einfgen?Insert as image?Composer"Als Link einfgenInsert as linkComposer8Als sichtbares Bild einfgenInsert as visible imageComposer Linie hinzufgen Insert lineComposer KursivItalicComposer ListeListComposerLink hinzufgen Make a linkComposerHLink aus ausgewhltem Text erstellenMake a link from selected textComposer NormalNormalComposer>Text ohne Formatierung einfgenPaste Text Without FormattingComposer0Vorformattierter BereichPreformatted blockComposer Zitat Quote blockComposerDurchgestrichen StrikethroughComposerSymboleSymbolsComposerTabelleTableComposerTabellengre Table SizeComposer.TextformateinstellungenText Formatting OptionsComposerDie Adresse, dass Sie hinzugefgt haben (%1) ist nicht gltig. Adressen sollen mit http:// oder https:// anfangen`The address you entered (%1) is not valid. Image addresses should begin with http:// or https://ComposerDer Link den Sie gerade einfgen scheint auf eine Bilddatei zu zeigen.4The link you are pasting seems to point to an image.ComposerDGeben Sie hier einen Kommentar einType a comment hereComposer@Schreiben Sie hier Ihren BeitragType a message here to post itComposerTippen oder fgen Sie hier eine Webadresse ein. Der ausgewhlte Text (%1) wird in einen Link umgewandelt.UType or paste a web address here. The selected text (%1) will be converted to a link.ComposerTippen oder fgen Sie hier eine Webadresse ein. Sie knnen auch zuerst Text auswhlen, um diesen in einen Link umzuwandeln.`Type or paste a web address here. You could also select some text first, to turn it into a link.ComposerTippen oder fgen Sie hier die Adresse fr ein Bild ein. Dieser Link muss direkt auf die Bilddatei verweisen.UType or paste the image address here. The link must point to the image file directly.ComposerUnterstrichen UnderlineComposer&Abbrechen&Cancel ConfigDialog*Be&wegliche Tableiste &Movable tabs ConfigDialogJBeitrge &pro Seite, Haupt-Zeitleiste&Posts per page, main timeline ConfigDialog0Konfiguration &speichern&Save Configuration ConfigDialog*Stelle fr &Tableiste&Tabs position ConfigDialogAlle Dateien All files ConfigDialog ImmerAlways ConfigDialog@Alle hervorgehobenen AktivittenAny highlighted activity ConfigDialog8Als SystembenachrichtigungenAs system notifications ConfigDialogAvatargre Avatar size ConfigDialog UntenBottom ConfigDialog FarbenColors ConfigDialogKommentareComments ConfigDialog6Benutzerdef&iniertes Symbol Custom &Icon ConfigDialog4Benutzerdefiniertes Symbol Custom icon ConfigDialogStandardDefault ConfigDialogRDianara speichert Daten in diesen Ordner:#Dianara stores data in this folder: ConfigDialog>Keine Benachrichtigungen zeigenDon't show notifications ConfigDialogFilterregelnFiltering rules ConfigDialogSchriftartenFonts ConfigDialog&Allgemeine OptionenGeneral Options ConfigDialogPhervorgehobenen Aktivitten im Nebenfeed$Highlighted activities in minor feed ConfigDialogNHervorgehobene Aktivitten, auer meine#Highlighted activities, except mine ConfigDialog2hervorgehobenen BeitrgenHighlighted posts ConfigDialogBilddateien Image files ConfigDialog&Ungltige Bilddatei Invalid image ConfigDialoghElement wurde wegen einer Filterregel hervorgehoben.(Item highlighted due to filtering rules. ConfigDialog Element ist neu. Item is new. ConfigDialogLinke Seite(tabs on left side/west; RTL not affected Left side ConfigDialogNebenfeed Minor Feeds ConfigDialog*NetzwerkkonfigurationNetwork configuration ConfigDialogNieNever ConfigDialog<neuen Aktivitten im NebenfeedNew activities in minor feed ConfigDialogneuen Beitrgen New posts ConfigDialog6Stil der BenachrichtigungenNotification Style ConfigDialog$Benachrichtigungen Notifications ConfigDialogBBenachrichtigen beim Empfang von:Notify when receiving: ConfigDialogBeitragsinhalte Post Contents ConfigDialogBeitragstitel Post Titles ConfigDialogBeitrgePosts ConfigDialogNBeitrge pr&o Seite, andere Zeitleisten Posts per page, &other timelines ConfigDialog(Pro&xy-EinstellungenPro&xy Settings ConfigDialog*ProgrammeinstellungenProgram Configuration ConfigDialogDffentliche Beitrge als Stan&dardPublic posts as &default ConfigDialogRechte Seite)tabs on right side/east; RTL not affected Right side ConfigDialogAus&whlen... S&elect... ConfigDialogHBenutzerdefiniertes Symbol auswhlenSelect custom icon ConfigDialog$F&ilter einrichtenSet Up F&ilters ConfigDialogDZustzliche Informationen anzeigenShow extra information ConfigDialog:Schnipsel im Nebenfeed zeigenShow snippets in minor feeds ConfigDialog>Ihren derzeitigen Avatar zeigenShow your current avatar ConfigDialog0Maximale Schnipsel-Lnge Snippet limit ConfigDialog8System-Benachrichtigungsfeld System Tray ConfigDialogXSymbol&typ frs System-BenachrichtigungsfeldSystem Tray Icon &Type ConfigDialogLSystemeigenes Iconset, falls verfgbarSystem iconset, if available ConfigDialogDie Aktivitt ist eine Reaktion auf eine Ihrer Aktionen, z.B. ein Kommentar, der als Antwort auf eine Ihrer Mitteilungen verffentlicht wurde.jThe activity is in reply to something done by you, such as a comment posted in reply to one of your notes. ConfigDialogDie Aktivitt bezieht sich auf eines Ihrer Objekte, z.B., wenn einer Ihrer Beitrge favorisiert wurde.YThe activity is related to one of your objects, such as someone liking one of your posts. ConfigDialogVDie ausgewhlte Bilddatei ist nicht gltig. The selected image is not valid. ConfigDialogPAkt&ualisierungsintervall der ZeitleisteTimeline &update interval ConfigDialogZeitleisten Timelines ConfigDialogObenTop ConfigDialogWird auch verwendet, um an Sie gerichtete Beitrge in den Zeitleisten hervorzuheben.DUsed also when highlighting posts addressed to you in the timelines. ConfigDialogWird auch verwendet, um Ihre eigenen Beitrge in den Zeitleisten hervorzuheben.Sie knnen private Nachrichten erstellen, indem Sie bestimmte Personen zu diesen Listen hinzufgen und die Optionen 'Anhngerschaft' und 'ffentlich' abwhlen.~You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. HelpWidgetSie finden eine Liste mit einigen Pump.io-Nutzern und anderen Informationen hier:GYou can find a list with some Pump.io users and other information here: HelpWidgetpSie knnen Mitteilungen verffentlichen, indem Sie in das Eingabefeld im oberen Fensterbereich klicken oder Strg+N drcken. Ihrem Beitrag einen Titel zu geben ist optional, jedoch sehr empfehlenswert, da es das Erkennen von Verweisen auf Ihren Beitrag im Nebenfeed, in Email-Benachrichtigungen usw., vereinfacht.You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. HelpWidgetSie knnen die Listen der Leute, denen Sie folgen, und derjenigen, die Ihnen folgen, im Kontakte-Tab anzeigen lassen.UYou can see the lists of people you follow, and who follow you from the Contacts tab. HelpWidgetIndem Sie die 'An'- und die 'Cc'-Schaltflche verwenden, bestimmen Sie wer Ihren Beitrag sehen wird.EYou can select who will see your post by using the To and Cc buttons. HelpWidgetSie knnen den Parameter '--config' verwenden um das Programm mit einer anderen Konfiguration auszufhren. Das kann ntzlich sein, um zwei oder mehr Konten zu verwenden. Sie knnen sogar zwei Instanzen Dianaras gleichzeitig ausfhren.You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. HelpWidgetFMittels der 'Format' Schaltflche knnen Sie Ihrem Text Formatierungen wie Fett- oder Kursivdruck verleihen. Manche dieser Optionen setzen eine Textauswahl voraus.You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. HelpWidgetjSie sollten einen Blick in das Fenster 'Programmeinstellungen' werfen, erreichbar ber das Men 'Einstellungen' - 'Dianara einrichten'. Dort finden Sie einige interessante Optionen.You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. HelpWidget&Schlieen&Close ImageViewer,Animation &neu starten&Restart Animation ImageViewer&&Speichern unter... &Save As... ImageViewerAlle Dateien All files ImageViewer(Betrachter schlieen Close Viewer ImageViewer@Fehler beim speichern des BildesError saving image ImageViewerBildImage ImageViewerBilddateien Image files ImageViewer.Bild speichern unter...Save Image As... ImageViewer"Bild speichern... Save Image... ImageViewerEin Fehler ist aufgetreten beim Speichern von %1. Der Dateiname sollte mit den Erweiterungen '.jpg' oder '.png' enden.UThere was a problem while saving %1. Filename should end in .jpg or .png extensions. ImageViewer*Zur Liste hin&zufgen &Add to List ListsManager4Ausgewhlte Li&ste lschen&Delete Selected List ListsManager &Nein&No ListsManager"Mitglied &lschen&Remove Member ListsManager&Ja&Yes ListsManager&Ja, lschen&Yes, delete it ListsManager(Mitglied &hinzufgen Add Mem&ber ListsManager,&Neue Liste hinzufgen Add New &List ListsManagerbSind Sie sich sicher, dass Sie %1 lschen wollen?#Are you sure you want to delete %1? ListsManagerSind Sie sich sicher, dass Sie %1 aus der Liste %2 lschen wollen?4Are you sure you want to remove %1 from the %2 list? ListsManager Liste &erstellen Create L&ist ListsManagerMitgliederMembers ListsManagerNameName ListsManager:Person aus der Liste lschen?Remove person from list? ListsManager^Geben Sie einen Namen fr die neue Liste ein...Type a name for the new list... ListsManager\Geben Sie eine optionale Beschreibung hier ein!Type an optional description here ListsManager.WARNUNG: Liste lschen?WARNING: Delete list? ListsManager&Schlieen&Close LogViewer"Log-Datei &leeren Clear &Log LogViewerLog-DateiLog LogViewer$%1 hervorgehobene.%1 highlighted. MainWindow$%1 hervorgehobene.plural, refers to posts%1 highlighted. MainWindow &Konto&Account MainWindow&Aktivitt &Activity MainWindow&Dianara &einrichten&Configure Dianara MainWindow&Kontakte &Contacts MainWindow.&Filter und Hervorheben&Filters and Highlighting MainWindow &Hilfe&Help MainWindow&Fenster &verstecken &Hide Window MainWindow&Log-Datei&Log MainWindow&Nachrichten &Messages MainWindow &Nein&No MainWindow6&Mitteilung verffentlichen &Post a Note MainWindow&Verlassen&Quit MainWindow&Sitzung&Session MainWindowFen&ster zeigen &Show Window MainWindowZeitleis&te &Timeline MainWindowWerkzeugleis&te&Toolbar MainWindow&Ansicht&View MainWindow.&Ja, Programm schlieen&Yes, close the program MainWindow$'%1' aktualisiert. '%1' updated. MainWindow"1 hervorgehobene.1 highlighted. MainWindow 1 hervorgehoben.singular, refers to a post1 highlighted. MainWindowber &DianaraAbout &Dianara MainWindowber Dianara About Dianara MainWindowLZei&tleisten automatisch aktualisierenAuto-update &Timelines MainWindowFAutomatisches Aktualisieren inaktivAuto-updating disabled MainWindowBAutomatisches Aktualisieren aktivAuto-updating enabled MainWindow(&Hilfe zu Grundlagen Basic &Help MainWindowNKlicken Sie um Ihr Profil zu bearbeitenClick to edit your profile MainWindowpDianara ist ein Client fr das soziale Netzwerk Pump.io..Dianara is a pump.io social networking client. MainWindowDianara ist unter der GNU-GPL-Lizenz lizenziert, und verwendet einige Symbole von Oxygen: http://www.oxygen-icons.org/ (LGPL lizenziert)wDianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) MainWindow$Dianara gestartet.Dianara started. MainWindowLWollen Sie Dianara wirklich schlieen?$Do you really want to close Dianara? MainWindow$&Profil bearbeiten Edit &Profile MainWindowxDeutsche bersetzung durch Eugenio M. Vigo und mightyscoopa.#English translation by JanKusanagi. MainWindowdGeben Sie das Passwort fr Ihren Proxy-Server ein:)Enter the password for your proxy server: MainWindowFavor&iten Favor&ites MainWindowVoll&bild Full &Screen MainWindowStarten...Initializing... MainWindowLink zu: %1 Link to: %1 MainWindow>Liste einiger P&ump.io-BenutzerList of Some Pump.io &Users MainWindow:Alles als 'gelesen' markierenMark All as Read MainWindow2Nachrichten direkt an SieMessages sent explicitly to you MainWindowFAn Sie gerichtete Neben-Aktivitten!Minor activities addressed to you MainWindowpNeben-Aktivitten von allen, z.B. Antworten auf BeitrgeStarting automatic update of timelines, once every %1 minutes. MainWindow&Statusleiste Status &Bar MainWindowfBeende automatische Aktualisierung der Zeitleisten.'Stopping automatic update of timelines. MainWindowGedankt sei allen Testern, bersetzern und Paketerstellern, die dabei helfen, Dianara zu verbessern!SThanks to all the testers, translators and packagers, who help make Dianara better! MainWindow(Die Haupt-ZeitleisteThe main timeline MainWindowDie Leute denen Sie folgen, diejenigen, die Ihnen folgen, und Ihre KontaklistenEThe people you follow, the ones who follow you, and your person lists MainWindow8Es gibt %1 neue Aktivitten.There are %1 new activities. MainWindow2Es gibt %1 neue Beitrge.There are %1 new posts. MainWindow2Es gibt 1 neue Aktivitt.There is 1 new activity. MainWindow0Es gibt 1 neuen Beitrag.There is 1 new post. MainWindow<Zeitleiste aktualisiert um %1.Timeline updated at %1. MainWindowWerkzeugleisteToolbar MainWindow&Beitrge gesamt: %1Total posts: %1 MainWindow$&Webseite besuchenVisit &Website MainWindowMit Dianara knnen Sie Ihre Zeitleisten betrachten, neue Mitteilungen schreiben, Bilder und andere Medien hochladen, mit Beitrgen interagieren, Ihre Kontakte verwalten und neuen Leuten folgen.With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. MainWindowtSie schreiben gerade eine Mitteilung oder einen Kommentar.&You are composing a note or a comment. MainWindowSie haben einen Proxy-Server mit Authentifizierung konfiguriert, aber das Passwort ist nicht eingestellt.TYou have configured a proxy server with authentication, but the password is not set. MainWindowPIhr Pump.io-Konto ist nicht konfiguriert&Your Pump.io account is not configured MainWindowNIhre Konto ist noch nicht konfiguriert.#Your account is not configured yet. MainWindow0Ihre Biographie ist leerYour biography is empty MainWindow6Ihre favorisierten BeitrgeYour favorited posts MainWindow*Ihre eigenen BeitrgeYour own posts MainWindowDVorherige Nebenaktivitten abrufenGet previous minor activities MinorFeed$ltere AktivittenOlder Activities MinorFeedREs gibt noch keine Aktivitten zu zeigen.$There are no activities to show yet. MinorFeed Cc: %1Cc: %1 MinorFeedItem0Erwhnten Beitrag ffnenOpen referenced post MinorFeedItem An: %1To: %1 MinorFeedItem Mit %1Using %1 MinorFeedItem Bytesbytes MiscHelpers&Abbrechen&Cancel PageSelector&Los&Go PageSelector"Zu Seite springen Jump to page PageSelector NeuerAs in: newer pagesNewer PageSelector lterAs in: older pagesOlder PageSelectorSeitenzahl: Page number: PageSelector&Abbrechen&Cancel PeopleWidget&Suche:&Search: PeopleWidgetNEinen Kontakt zu einer Liste hinzufgenAdd a contact to a list PeopleWidgetfGeben Sie hier einen Namen ein, um danach zu suchen"Enter a name here to search for it PeopleWidget%1 Kommentare %1 commentsPost%1 gefllt dies %1 like thisPost%1x favorisiert%1 likesPost%1 gefllt dies %1 likes thisPost6%1 Mitglieder in der Gruppe%1 members in the groupPost&%1 hat dies geteilt%1 shared thisPost*%1 haben dies geteilt#%1 = Names for more than one person%1 shared thisPostS&chlieen&ClosePost &Nein&NoPost&Ja, lschen&Yes, delete itPost&Ja, teilen&Yes, share itPost,&Ja, nicht mehr teilen&Yes, unshare itPost1 Kommentar 1 commentPost1x favorisiert1 likePostpSind Sie sicher, dass Sie diesen Beitrag lschen wollen?*Are you sure you want to delete this post?Post*Angehngte AudiodateiAttached AudioPost Angehngte Datei Attached FilePost*Angehngte VideodateiAttached VideoPostCcCcPostpKlicken Sie auf das Bild, um es in voller Gre zu sehen&Click the image to see it in full sizePostLKlicken, um den Anhang herunterzuladen Click to download the attachmentPostKommentierenCommentPost\Kopiere den Beitragslink in die ZwischenablageCopy post link to clipboardPostLschenDeletePost<Wollen Sie %1s Beitrag teilen?Do you want to share %1's post?PostpWollen Sie das Teilen von %1s Beitrag rckgngig machen?!Do you want to unshare %1's post?PostBearbeitenEditPostBearbeitet: %1 Edited: %1Post,Diesen Beitrag lschenErase this postPost>Ausgewhlter Text wird zitiert.+If you select some text, it will be quoted.PosthBild ist animiert. Darauf klicken um es abzuspielen.'Image is animated. Click on it to play.PostInInPost Gruppe beitreten Join GroupPostGefllt mirLikePost8Diese Nachricht favorisierenLike this postPostLade Bild...Loading image...Post"Am %1 modifiziertModified on %1Post2Diesen Beitrag bearbeitenModify this postPost4Normalisiere SchriftfarbenNormalize text colorsPost@Diesen Beitrag im Browser ffnenOpen post in web browserPost`ffne Vorluferbeitrag, auf den dieser antwortet/Open the parent post, to which this one repliesPostVorluferParentPostBeitragPostPost(Am %1 verffentlicht Posted on %1Post:Auf diesen Beitrag antworten.Reply to this post.Post TeilenSharePostBeitrag teilen? Share post?PostRDiesen Beitrag mit Ihren Kontakten teilen"Share this post with your contactsPost%1x geteiltShared %1 timesPostGeteilt am %1 Shared on %1Post1x geteilt Shared oncePostAnToPostTypTypePost"Gefllt mir nichtUnlikePost"Nicht mehr teilenUnsharePost4Beitrag nicht mehr teilen? Unshare post?Post@Diesen Beitrag nicht mehr teilenUnshare this postPost Mit %1Using %1Post Via %1Via %1Post2WARNUNG: Beitrag lschen?WARNING: Delete post?Post0Du hast dies favorisiert You like thisPost&Biographie&Bio ProfileEditor&Abbrechen&Cancel ProfileEditor&Heimatort &Hometown ProfileEditor"Profil &speichern &Save Profile ProfileEditorAlle Dateien All files ProfileEditor AvatarAvatar ProfileEditor"&Avatar ndern...Change &Avatar... ProfileEditor E-MailE-mail ProfileEditorGanzer &Name Full &Name ProfileEditorBilddateien Image files ProfileEditor&Ungltige Bilddatei Invalid image ProfileEditor"Nicht eingestelltNot set ProfileEditorProfil-EditorProfile Editor ProfileEditor2Bild fr Avatar auswhlenSelect avatar image ProfileEditorVDie ausgewhlte Bilddatei ist nicht gltig. The selected image is not valid. ProfileEditorDies ist die mit Ihrem Konto verknpfte E-Mail-Adresse, um Benachrichtigungen zu erhalten und fr die PasswortwiederherstellungoThis is the e-mail address associated with your account, for things such as notifications and password recovery ProfileEditor:Dies ist Ihre Pump.io-AdresseThis is your Pump address ProfileEditor8Dies ist Ihr sichtbarer NameThis is your visible name ProfileEditorWebfinger-ID Webfinger ID ProfileEditor&Abbrechen&Cancel ProxyDialog&Host-Name &Hostname ProxyDialog &Port&Port ProxyDialog&Speichern&Save ProxyDialogBen&utzer&User ProxyDialog,Keinen Proxy verwendenDo not use a proxy ProxyDialog6Hinweis: Das Passwort wird nicht sicher hinterlegt. Wenn Sie wollen, knnen Sie das Feld leer lassen, dann werden Sie beim Start nach dem Passwort gefragt.Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. ProxyDialogPass&wort Pass&word ProxyDialogProxy-&Typ Proxy &Type ProxyDialog&Proxy-KonfigurationProxy Configuration ProxyDialog8&Authentifizierung verwendenUse &Authentication ProxyDialog,Ihr Proxy-BenutzernameYour proxy username ProxyDialog:%1 KiB von %2 KiB hochgeladen%1 KiB of %2 KiB uploaded Publisher<&Abbrechen, zurck zum Beitrag&Cancel, go back to the post Publisher\&Nein, nur an 'Anhngerschaft' verffentlichen&No, post to my followers only Publisher,&Ja, ffentlich machen&Yes, make it public PublisherHin&zufgen...Ad&d... PublishertFgen Sie dem Beitrag einen kurzen Titel hinzu (empfohlen)1Add a brief title for the post here (recommended) PublisherAlle Dateien All files Publisher AudioAudio Publisher0Audiodatei nicht gewhltAudio file not set PublisherAudiodateien Audio files PublisherAbbrechenCancel Publisher"Beitrag verwerfenCancel the post Publisher Cc...Cc... Publisher$Dianara begrenzt das Hochladen von Dateien z.Zt. auf 10 MiB pro Beitrag, um mgliche Speicherplatz- oder Netzwerkprobleme der Server zu vermeiden.yDianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. PublisherWollen Sie den Beitrag ffentlich, anstatt nur fr Ihre 'Anhngerschaft' machen?>Do you want to make the post public instead of followers-only? Publisher"Bearbeite Beitrag Editing post Publisher8Fehler: Bereits am ErstellenError: Already composing Publisher*Die Datei ist zu groFile is too big Publisher.Datei nicht ausgewhlt.File not selected. Publisher&Datei nicht gewhlt File not set PublisherDAudiodatei in Ihren Ordnern finden#Find the audio file in your folders Publisher:Datei in Ihren Ordnern findenFind the file in your folders Publisher8Bild in Ihren Ordnern finden Find the picture in your folders Publisher:Video in Ihren Ordnern findenFind the video in your folders PublisherAnhngerschaft Followers PublisherDrcken Sie Strg+Eingabe, um mit der Tastatut zu verffentlichen+Hit Control+Enter to post with the keyboard PublisherWenn Sie so verffentlichen, wird niemand Ihren Beitrag sehen knnen.?If you post like this, no one will be able to see your message. PublisherBilddateien Image files Publisher(Ungltige AudiodateiInvalid audio file PublisherUngltige Datei Invalid file Publisher&Ungltige Bilddatei Invalid image Publisher(Ungltige VideodateiInvalid video file Publisher ListenLists PublisherSonstigesOther PublisherLeute... People... PublisherBildPicture Publisher$Bild nicht gewhltPicture not set PublisherVerffentlichenPost Publisher*Der Beitrag ist leer.Post is empty. PublisherfVerffentlichung fehlgeschlagen. Erneut versuchen.Posting failed. Try again. Publisher"Verffentliche... Posting... PublisherffentlichPublic PublisherAuflsung Resolution Publisher.Audiodatei auswhlen...Select Audio File... Publisher$Datei auswhlen...Select File... Publisher"Bild auswhlen...Select Picture... Publisher$Video auswhlen...Select Video... Publisher<Whlen Sie eine Audiodatei ausSelect one audio file Publisher2Whlen Sie eine Datei ausSelect one file Publisher.Whlen Sie ein Bild ausSelect one image Publisher@Whlen Sie _eine_ Videodatei ausSelect one video file PublishernAuswhlen, wer eine Kopie dieses Beitrags erhalten soll'Select who will get a copy of this post PublisherRAuswhlen, wer diesen Beitrags sehen sollSelect who will see this post PublisherEinen Titel einzustellen hilft dabei die 'Inzwischen'-Zeitleiste informativer zu machen>Setting a title helps make the Meanwhile feed more informative PublisherDa Sie ein Bild hochladen wollen, knnten Sie es ein wenig verkleinern oder es in einem komprimierten Format wie JPG speichern.sSince you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. Publisher GreSize PublisherREntschuldigen Sie die Unannehmlichkeiten.Sorry for the inconvenience. PublisherZDas Format der Audiodatei wird nicht erkannt.$The audio format cannot be detected. Publisher@Der Dateityp wird nicht erkannt.!The file type cannot be detected. Publisher(Das Format der Bilddatei wird nicht erkannt. Vielleicht ist deren Erweiterung falsch, bspw. eine GIF-Datei die nach 'image.jpg' umbenannt wurde o..tThe image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. PublisherZDas Format der Videodatei wird nicht erkannt.$The video format cannot be detected. PublisherDies ist eine vorlufige Manahme, da die Server noch nicht ihre eigenen Begrenzungen aufstellen knnen.OThis is a temporary measure, since the servers cannot set their own limits yet. Publisher TitelTitle Publisher An...To... PublisherTypType PublisherAktualisierenUpdate PublisherAktualisiere... Updating... PublisherRMedien, wie Bilder oder Videos, hochladen%Upload media, like pictures or videos Publisher VideoVideo PublisherVideodateien Video files Publisher&Video nicht gewhlt Video not set PublisherXWarnung: Sie haben noch keine Anhngerschaft"Warning: You have no followers yet PublisherSie knnen jetzt keine Nachricht an %1 erstellen, weil bereits ein anderer Beitrag erstellt wird.YYou can't create a message for %1 at this time, because a post is already being composed. PublisherSie knnen jetzt keinen Beitrag bearbeiten, weil bereits ein anderer Beitrag erstellt wird.MYou can't edit a post at this time, because a post is already being composed. PublisherSie versuchen nur an Ihre Anhngerschaft zu verffentlichen, aber Sie haben noch keine.SYou're trying to post to your followers only, but you don't have any followers yet. PublisherR%1 (%2) erfolgreich zu Liste hinzugefgt.#%1 (%2) added to list successfully.PumpControllerN%1 (%2) erfolgreich von Liste entfernt.'%1 (%2) removed from list successfully.PumpController0%1 Kommentare empfangen.%1 comments received.PumpController,1 Kommentar empfangen.1 comment received.PumpControllerAktionenActionsPumpControllerAktivittActivityPumpController:Fge Person zu Liste hinzu...Adding person to list...PumpControllerAlle einleitenden Daten empfangen. Initialisierung abgeschlossen.3All initial data received. Initialization complete.PumpControllerDAnwendung erfolgreich autorisiert.$Application authorized successfully.PumpController(AutorisierungsfehlerAuthorization errorPumpControllerhAutorisiert fr Konto %1. Rufe einleitende Daten ab.3Authorized to use account %1. Getting initial data.PumpControllerDAvatar erfolgreich verffentlicht.Avatar published successfully.PumpController&Avatar hochgeladen.Avatar uploaded.PumpControllerFalsche Anfrage Bad RequestPumpController$Erstelle Gruppe...Creating group...PumpController2Erstelle Personenliste...Creating person list...PumpController.Lsche Personenliste...Deleting person list...PumpController8Fehler beim verbinden mit %1Error connecting to %1PumpControllerFavoriten FavoritesPumpControllerDDatei erfolgreich heruntergeladen.File downloaded successfully.PumpControllerpDatei erfolgreich hochgeladen. Verffentliche Beitrag....File uploaded successfully. Posting message...PumpControllerAnhngerschaft FollowersPumpControllerVerfolgte FollowingPumpController>Folgen von %1 (%2) erfolgreich.Following %1 (%2) successfully.PumpControllerVerboten ForbiddenPumpController4Gateway-ZeitberschreitungGateway TimeoutPumpController,Rufe Oauth-Token ab...Getting OAuth token...PumpController:Rufe eine Personenliste ab...Getting a person list...PumpController*Rufe Kommentare ab...Getting comments...PumpController4Rufe Favorisierungen ab...Getting likes...PumpControllerBRufe Liste 'Anhngerschaft' ab...Getting list of 'Followers'...PumpController8Rufe Liste 'Verfolgte' ab...Getting list of 'Following'...PumpControllerFRufe Liste der Personenlisten ab...Getting list of person lists...PumpControllerVerschwundenGonePumpController>Gruppe %1 erfolgreich erstellt.Group %1 created successfully.PumpControllerDGruppe %1 erfolgreich beigetreten.Group %1 joined successfully.PumpControllerHTTP-Fehler HTTP errorPumpController*Interner ServerfehlerInternal Server ErrorPumpController&Trete Gruppe bei...Joining group...PumpController$Verlasse Gruppe...Leaving group...PumpController@Gruppe %1 erfolgreich verlassen.Left the %1 group successfully.PumpController4Favorisierungen empfangen.Likes received.PumpControllerZListe 'Anhngerschaft' vollstndig empfangen.(List of 'followers' completely received.PumpControllerPListe 'Verfolgte' vollstndig empfangen.(List of 'following' completely received.PumpController8Liste der 'Listen' erhalten.List of 'lists' received.PumpControllerInzwischen MeanwhilePumpControllerErwhnungenMentionsPumpController<Beitrge erfolgreich gelscht.Message deleted successfully.PumpControllerHBeitrag erfolgreich (de)favorisiert.&Message liked or unliked successfully.PumpControllerNachrichtenMessagesPumpController(Permanent verschobenMoved PermanentlyPumpController&Temporr verschobenMoved TemporarilyPumpControllerNicht gefunden Not FoundPumpController&Nicht implementiertNot ImplementedPumpControllerjOAuth-Fehler whrend des Autorisierens der Anwendung.*OAuth error while authorizing application.PumpController(OAuth-Support-FehlerOAuth support errorPumpControllerVListe 'Anhngerschaft' teilweise empfangen.%Partial list of 'followers' received.PumpControllerLListe 'Verfolgte' teilweise empfangen.%Partial list of 'following' received.PumpControllerPPersonenliste '%1' erfolgreich erstellt.&Person list '%1' created successfully.PumpControllerFPersonenliste erfolgreich gelscht.!Person list deleted successfully.PumpController0Personenliste empfangen.Person list received.PumpControllerFBeitrag von %1 erfolgreich geteilt.Post by %1 shared successfully.PumpController"Profil empfangen.Profile received.PumpController(Profil aktualisiert.Profile updated.PumpController QOAuth-Fehler %1QOAuth error %1PumpControllerBereit.Ready.PumpController8Entferne Person von Liste...Removing person from list...PumpController,Dienst nicht verfgbarService UnavailablePumpController|Einige einleitenden Daten wurden nach mehreren Versuchen nicht empfangen. Vielleicht stimmt etwas mit Ihrem Server nicht. Dennoch ist es mglich, dass Sie den Dienst normal verwenden knnen.Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally.PumpControllerEinige einleitenden Daten wurden nicht empfangen. Beginne erneute Initialisierung...@Some initial data was not received. Restarting initialization...PumpControllerbWarte weiterhin auf Profil. Versuche es erneut...*Still waiting for profile. Trying again...PumpControllerNFolgen von %1 (%2) erfolgreich beendet.'Stopped following %1 (%2) successfully.PumpControllerDie Anwendung ist noch nicht bei Ihrem Server registriert. Registriere...FThe application is not registered with your server yet. Registering...PumpControllerBEs gibt kein autorisiertes Konto.There is no authorized account.PumpControllerEin OAuth-Fehler trat auf, whrend versucht wurde das Autorisierungs-Token abzurufen.EThere was an OAuth error while trying to get the authorization token.PumpControllerZeitleisteTimelinePumpController"Nicht autorisiert UnauthorizedPumpControllerBUnbehandelter HTTP-Fehler-Code %1Unhandled HTTP error code %1PumpControllerLade %1 hoch Uploading %1PumpController6Warte auf Proxy-Passwort...Waiting for proxy password...PumpControllerSie mssen wahrscheinlich das OpenSSL-Plugin fr QCA installieren: %1, %2 o..KYou probably need to install the OpenSSL plugin for QCA: %1, %2 or similar.PumpControllerIhre Installation von QOAuth, einer von Dianara verwendeten Bibliothek, scheint HMAC-SHA1 nicht zu untersttzen._Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support.PumpController,%1 Beitrge insgesamt.%1 posts in total.TimeLine(Aktivitt-ZeitleisteActivity TimelineTimeLineNachdem der Vorgang abgeschlossen ist, sollten sich Ihr Profil und Ihre Zeitleisten automatisch aktualisieren.RAfter the process is done, your profile and timelines should update automatically.TimeLineKlicken Sie hier oder drcken Sie Str+G, um zu einer bestimmten Seite zu springen8Click here or press Control+G to jump to a specific pageTimeLineLDianara ist ein <b>Pump.io</b>-Client.#Dianara is a Pump.io client.TimeLineDianaras BlogDianara's blogTimeLine8Direktnachrichten-ZeitleisteDirect Messages TimelineTimeLine(Favoriten ZeitleisteFavorites TimelineTimeLineAls Erstes sollten Sie Ihr Konto ber das Men <b>Einstellungen - Konto</b> einrichten.FFirst, configure your account from the Settings - Account menu.TimeLinetHier werden Sie speziell an Sie gerichtete Beitrge sehen.4Here, you'll see posts specifically directed to you.TimeLineFalls Sie noch kein Pump.io-Konto haben, knnen Sie z.B. bei folgenden Adressen eines bekommen:]If you don't have a Pump account yet, you can get one at the following address, for instance:TimeLine NeuerNewerTimeLineNeuesteNewestTimeLine lterOlderTimeLine Seite %1 von %2.Page %1 of %2.TimeLinefBeitrge und Kommentare, die Sie favorisiert haben. Posts and comments you've liked.TimeLineDrcken Sie <b>F1</b>, falls Sie das Hilfe-Fenster ffnen wollen.4Press F1 if you want to open the Help window.TimeLine"Pump.io-LeitfadenPump.io User GuideTimeLine8Zeige %1 Beitrge pro Seite.Showing %1 posts per page.TimeLineNehmen sich einen Moment, um sich in den Mens und im Konfigurationsfenster umzuschauen.DTake a moment to look around the menus and the Configuration window.TimeLine Es gibt berall Tooltips, sodass Sie beim berfahren einer Schaltflche oder eines Eingabefeldes wahrscheinlich zustzliche Informationen sehen.There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information.TimeLine,Willkommen bei DianaraWelcome to DianaraTimeLineSie knnen auch Profilinformationen und -bild ber das Men <b>Einstellungen - Profil bearbeiten</b> einstellen.\You can also set your profile data and picture from the Settings - Edit Profile menu.TimeLineXHier werden Sie Ihre eigenen Beitrge sehen.You'll see your own posts here.TimeLineVor %1 Tagen %1 days ago TimestampVor %1 Stunden %1 hours ago TimestampVor %1 Minuten%1 minutes ago TimestampVor %1 Monaten %1 months ago TimestampVor %1 Jahren %1 years ago Timestamp Vor einer Minute A minute ago TimestampVor einem Monat A month ago TimestampVor einem Jahr A year ago Timestamp Vor einer Stunde An hour ago TimestampIn der Zukunft In the future Timestamp.Ungltiger Zeitstempel!Invalid timestamp! TimestampGerade jetztJust now TimestampGestern Yesterday Timestamp&Schlieen&Close UserPostsdianara-v1.3.2/translations/dianara_ru.ts000644 000764 000764 00000553476 12614520350 020114 0ustar00janjan000000 000000 ASActivity Public %1 by %2 1=kind of object: note, comment, etc; 2=author's name ASObject Note Noun, an object type Article Noun, an object type Image Noun, an object type Audio Noun, an object type Video Noun, an object type File Noun, an object type Comment Noun, as in object type: a comment Group Noun, an object type Collection Noun, an object type Other As in: other type of post No detailed location Deleted on %1 and one other and %1 others ASPerson Hometown AccountDialog Your Pump.io address: Get &Verifier Code Verifier code: Enter or paste the verifier code provided by your Pump server here &Save Details If the browser doesn't open automatically, copy this address manually Account Configuration First, enter your Webfinger ID, your pump.io address. Your address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. If your profile is at https://pump.example/yourname, then your address is yourname@pump.example If you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. 1=link to website If you need help: %1 Pump.io User Guide Your address, like username@pumpserver.org After clicking this button, a web browser will open, requesting authorization for Dianara Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. Don't translate the VERIFIER word! &Authorize Application &Cancel Your account is properly configured. Press Unlock if you wish to configure a different account. &Unlock A web browser will start now, where you can get the verifier code Your Pump address is invalid Verifier code is empty Dianara is authorized to access your data AudienceSelector 'To' List 'Cc' List &Add to Selected All Contacts Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. ON THE LEFT should change to ON THE RIGHT in RTL languages Clear &List &Done &Cancel Selected People AvatarButton Open %1's profile in web browser Open your profile in web browser Send message to %1 Browse messages Stop following Follow Stop following? Are you sure you want to stop following %1? &Yes, stop following &No ColorPicker Change Choose a color Comment Posted on %1 Modified on %1 Like or unlike this comment Quote This is a verb, infinitive Reply quoting this comment Edit Modify this comment Delete Erase this comment Unlike Like %1 like this comment Plural: %1=list of people like John, Jane, Smith %1 likes this comment Singular: %1=name of just 1 person WARNING: Delete comment? Are you sure you want to delete this comment? &Yes, delete it &No CommenterBlock You can press Control+Enter to send the comment with the keyboard Reload comments Comment Infinitive verb Cancel Press ESC to cancel the comment if there is no text Check for comments Show all %1 comments Comments are not available Error: Already composing You can't edit a comment at this time, because another comment is already being composed. Editing comment Posting comment failed. Try again. Sending comment... Updating comment... Comment is empty. Composer Type a message here to post it Click here or press Control+N to post a note... Symbols Formatting Normal Bold Italic Underline Strikethrough Header List Table Preformatted block Quote block Make a link Insert an image from a web site Insert line &Format Button for text formatting and related options Text Formatting Options Paste Text Without Formatting Type a comment here Insert as image? The link you are pasting seems to point to an image. Insert as visible image Insert as link Table Size How many rows (height)? How many columns (width)? Insert a link Type or paste a web address here. You could also select some text first, to turn it into a link. Make a link from selected text Type or paste a web address here. The selected text (%1) will be converted to a link. Insert an image from a URL Type or paste the image address here. The link must point to the image file directly. Error: Invalid URL The address you entered (%1) is not valid. Image addresses should begin with http:// or https:// Cancel message? Are you sure you want to cancel this message? &Yes, cancel it &No ConfigDialog minutes Top Bottom Program Configuration Timeline &update interval posts Goes after a number, as: 25 posts &Posts per page, main timeline posts This goes after a number, like: 10 posts Posts per page, &other timelines &Tabs position &Movable tabs Public posts as &default Pro&xy Settings Network configuration Set Up F&ilters Filtering rules Highlighted activities, except mine Any highlighted activity Always Never Comments Left side tabs on left side/west; RTL not affected Right side tabs on right side/east; RTL not affected You are among the recipients of the activity, such as a comment addressed to you. Used also when highlighting posts addressed to you in the timelines. The activity is in reply to something done by you, such as a comment posted in reply to one of your notes. You are the object of the activity, such as someone adding you to a list. The activity is related to one of your objects, such as someone liking one of your posts. Used also when highlighting your own posts in the timelines. Item highlighted due to filtering rules. Item is new. Show snippets in minor feeds Show information for deleted posts Hide duplicated posts Jump to new posts line on update Avatar size Show extended share information Show extra information Highlight post author's comments Highlight your own comments Ignore SSL errors in images Show character counter Don't inform followers when following someone Don't inform followers when handling lists As system notifications Using own notifications Don't show notifications Notification Style Notify when receiving: New posts Highlighted posts New activities in minor feed Highlighted activities in minor feed Default System iconset, if available Show your current avatar Custom icon System Tray Icon &Type S&elect... Custom &Icon Hide window on startup Timelines Posts Composer Privacy Notifications System Tray This is a system notification System notifications are not available! Own notifications will be used. This is a basic notification Select custom icon Post Titles characters This is a suffix, after a number Snippet limit Post Contents Minor Feeds Only for images inserted from web sites. Use with care. Use attachment filename as initial post title Inform only the author when liking things General Options Fonts Colors Dianara stores data in this folder: &Save Configuration &Cancel Image files All files Invalid image The selected image is not valid. ContactCard Hometown Joined: %1 Updated: %1 Bio for %1 Abbreviation for Biography, but you can use the full word; %1=contact name This user doesn't have a biography No biography for %1 %1=contact name Open Profile in Web Browser Send Message Browse Messages In Lists... User Options Follow Stop Following Stop following? Are you sure you want to stop following %1? &Yes, stop following &No ContactList Type a partial name or ID to find a contact... F&ull List ContactManager username@server.org or https://server.org/username &Enter address to follow: &Follow Reload Followers Reload Following Export Followers Export Following Reload Lists &Neighbors Follo&wers Followin&g &Lists Export list of 'following' to a file Export list of 'followers' to a file DownloadWidget Download Save the attached file to your folders Cancel Save File As... All files Abort download? Do you want to stop downloading the attached file? &Yes, stop &No, continue Download aborted Download completed Download failed Downloading %1 KiB... %1 KiB downloaded EmailChanger Change E-mail Address Change &Cancel E-mail Address: Again: Your Password: E-mail addresses don't match! Password is empty! FilterEditor Filter Editor %1 if %2 contains: %3 This explains a filter rule, like: Hide if Author ID contains JohnDoe Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. Hide Highlight Post Contents Author ID Application Activity Description Keywords... &Add Filter Filters in use &Remove Selected Filter &Save Filters &Cancel if contains &New Filter C&urrent Filters FirstRunWizard Welcome Wizard Welcome to Dianara! This wizard will help you get started. You can access this window again at any time from the Help menu. The first step is setting up your account, by using the following button: Configure your &account Once you have configured your account, it's recommended that you edit your profile and add an avatar and some other information, if you haven't done so already. &Edit your profile By default, Dianara will post only to your followers, but it's recommended that you post to Public, at least sometimes. Post to &Public by default Open general program &help window &Show this again next time Dianara starts &Close FontPicker Change Choose a font HelpWidget Basic Help Getting started The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. At this point, your profile, contact lists and timelines will be loaded. You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. Settings You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. Timelines Contents Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. If you're new to Pump.io, take a look at this guide: Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. The main timeline, where you'll see all the stuff posted or shared by the people you follow. Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. Activity timeline, where you'll see your own posts, or posts shared by you. Favorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. Posting New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. It is possible to attach images, audio, video, and general files, like PDF documents, to your post. You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. If you add a specific person to the 'To' list, they will receive your message in their direct messages tab. Choose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. Managing contacts You can see the lists of people you follow, and who follow you from the Contacts tab. There, you can also manage person lists, used mainly to send posts to specific groups of people. You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. Keyboard controls Pump.io User Guide There are seven timelines: The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. LEFT SIDE should change to RIGHT SIDE on RTL languages The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). You can select who will see your post by using the To and Cc buttons. You can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. There is a text field at the top, where you can directly enter addresses of new contacts to follow them. You can also send a direct message (initially private) to that contact from this menu. You can find a list with some Pump.io users and other information here: Users by language The most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. Besides that, you can use: Control+Up/Down/PgUp/PgDown/Home/End to move around the timeline. Control+Left/Right to jump one page in the timeline. Control+G to go to any page in the timeline directly. Control+1/2/3 to switch between the minor feeds. Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. Dianara offers a D-Bus interface that allows some control from other applications. The interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. Command line options Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. Use the --debug parameter to have extra information in your terminal window, about what the program is doing. If your server does not support HTTPS, you can use the --nohttps parameter. If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. &Close ImageViewer Image &Save As... &Restart Animation &Close Save Image... Close Viewer Save Image As... Image files All files Error saving image There was a problem while saving %1. Filename should end in .jpg or .png extensions. ListsManager Name Members Add Mem&ber &Remove Member &Delete Selected List Add New &List Create L&ist &Add to List Are you sure you want to delete %1? 1=Name of a person list Remove person from list? Are you sure you want to remove %1 from the %2 list? 1=Name of a person, 2=name of a list &Yes Type a name for the new list... Type an optional description here WARNING: Delete list? &Yes, delete it &No LogViewer Log Clear &Log &Close MainWindow Side &Panel Status &Bar &Timeline The main timeline &Activity Your own posts Your favorited posts &Messages Messages sent explicitly to you &Contacts Initializing... Your account is not configured yet. Dianara started. Minor activities done by everyone, such as replying to posts Minor activities addressed to you Minor activities done by you The people you follow, the ones who follow you, and your person lists Press F1 for help Running with Qt v%1. Click here to configure your account &Session Auto-update &Timelines Mark All as Read &Post a Note &Quit &View &Toolbar Full &Screen &Log S&ettings Edit &Profile &Account Basic &Help Report a &Bug Pump.io User &Guide Some Pump.io &Tips List of Some Pump.io &Users Pump.io &Network Status Website Toolbar Open the log viewer Auto-updating enabled Auto-updating disabled Proxy password required You have configured a proxy server with authentication, but the password is not set. Enter the password for your proxy server: Starting automatic update of timelines, once every %1 minutes. Stopping automatic update of timelines. Received %1 older posts in '%2'. %1 is a number, %2 = name of a timeline 1 more pending to receive. singular, one post %1 more pending to receive. plural, several posts Also: Last update: %1 '%1' updated. %1 is the name of a feed Show Welcome Wizard 1 filtered out. singular, refers to a post %1 filtered out. plural, refers to posts 1 deleted. singular, refers to a post %1 deleted. plural, refers to posts There is 1 new activity. There are %1 new activities. 1 highlighted. singular, refers to an activity %1 highlighted. plural, refers to activities 1 filtered out. singular, refers to one activity %1 filtered out. plural, several activities No new activities. Error storing image! %1 bytes Marking everything as read... Closing due to environment shutting down... Quit? You are composing a note or a comment. Do you really want to close Dianara? &Yes, close the program &No Shutting down Dianara... System tray icon is not available. Dianara cannot be hidden in the system tray. Do you want to close the program completely? Timeline updated at %1. Update %1 Your Pump.io account is not configured Link to: %1 With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. English translation by JanKusanagi. TRANSLATORS: Change this with your language and name. If there was another translator before you, add your name after theirs ;) Dianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) &Configure Dianara &Filters and Highlighting &Help Visit &Website About &Dianara Your biography is empty Click to edit your profile 1 highlighted. singular, refers to a post %1 highlighted. plural, refers to posts No new posts. Total posts: %1 Favor&ites Received %1 older activities in '%2'. %1 is a number, %2 = name of feed Minor feed updated at %1. 1 more pending to receive. singular, 1 activity %1 more pending to receive. plural, several activities &Hide Window &Show Window There is 1 new post. There are %1 new posts. About Dianara Dianara is a pump.io social networking client. Thanks to all the testers, translators and packagers, who help make Dianara better! MinorFeed Older Activities Get previous minor activities There are no activities to show yet. Get %1 newer As in: Get 3 newer (activities) MinorFeedItem Using %1 Application used to generate this activity To: %1 1=people to whom this activity was sent Cc: %1 1=people to whom this activity was sent as CC Open referenced post MiscHelpers bytes PageSelector Jump to page Page number: &First As in: first page &Last As in: last page Newer As in: newer pages Older As in: older pages &Go &Cancel PeopleWidget &Search: Enter a name here to search for it Add a contact to a list &Cancel Post Like Like this post 1 like 1 comment Shared %1 times Shared on %1 Edited: %1 In Using %1 1=Program used for posting or sharing Share Edit Loading image... %1 likes %1 comments Delete Post Noun, not verb Via %1 Posted on %1 1=Date To If you select some text, it will be quoted. Unshare Unshare this post Open post in web browser Click to download the attachment Cc Copy post link to clipboard Normalize text colors &Close Type As in: type of object Modified on %1 Parent As in 'Open the parent post'. Try to use the shortest word! Open the parent post, to which this one replies Comment verb, for the comment button Reply to this post. Share this post with your contacts Modify this post Erase this post Join Group %1 members in the group Image is animated. Click on it to play. Couldn't load image! Attached Audio Attached Video Attached File %1 likes this One person %1 like this More than one person %1 shared this %1 = One person name %1 shared this %1 = Names for more than one person Shared once You like this Unlike Share post? Do you want to share %1's post? &Yes, share it &No Unshare post? Do you want to unshare %1's post? &Yes, unshare it WARNING: Delete post? Are you sure you want to delete this post? &Yes, delete it Click the image to see it in full size ProfileEditor Profile Editor This is your Pump address This is the e-mail address associated with your account, for things such as notifications and password recovery Change &E-mail... Change &Avatar... This is your visible name &Save Profile &Cancel Webfinger ID E-mail Avatar Full &Name &Hometown &Bio Not set In reference to the e-mail not being set for the account Select avatar image Image files All files Invalid image The selected image is not valid. ProxyDialog Proxy Configuration Do not use a proxy Your proxy username Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. &Save &Cancel Proxy &Type &Hostname &Port Use &Authentication &User Pass&word Publisher Title Select Picture... Find the picture in your folders Public Add a brief title for the post here (recommended) Remove Cancel the attachment, and go back to a regular note Followers Lists To... Select who will get a copy of this post Other as in other kinds of files Cancel Cancel the post Picture not set Select Audio File... Find the audio file in your folders Audio file not set Select Video... Find the video in your folders Video not set Select File... Find the file in your folders File not set Error: Already composing You can't edit a post at this time, because a post is already being composed. Update Editing post You can't create a message for %1 at this time, because a post is already being composed. Posting failed. Try again. Warning: You have no followers yet You're trying to post to your followers only, but you don't have any followers yet. If you post like this, no one will be able to see your message. Do you want to make the post public instead of followers-only? &Yes, make it public &Cancel, go back to the post Updating... Post is empty. File not selected. Select one image Image files Select one file Invalid file The file type cannot be detected. All files Since you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. File is too big Dianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. This is a temporary measure, since the servers cannot set their own limits yet. Sorry for the inconvenience. Resolution Type Size %1 KiB of %2 KiB uploaded Invalid image Setting a title helps make the Meanwhile feed more informative Cc... Post verb Note started from another application. Ignoring new note request from another application. &No, post to my followers only The image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. Select one audio file Audio files Invalid audio file The audio format cannot be detected. Select one video file Video files Invalid video file The video format cannot be detected. Posting... People... Select who will see this post Picture Audio Video Ad&d... Upload media, like pictures or videos Hit Control+Enter to post with the keyboard PumpController Creating person list... Deleting person list... Getting a person list... Adding person to list... Removing person from list... Creating group... Joining group... Leaving group... Getting likes... Getting comments... Activity Favorites Meanwhile Mentions Actions Uploading %1 1=filename HTTP error For the following HTTP error codesyou can check http://en.wikipedia.org/wiki/List_of_HTTP_status_codes in your language Gateway Timeout HTTP 504 error string Service Unavailable HTTP 503 error string Not Implemented HTTP 501 error string Internal Server Error HTTP 500 error string Gone HTTP 410 error string Not Found HTTP 404 error string Forbidden HTTP 403 error string Unauthorized HTTP 401 error string Bad Request HTTP 400 error string Moved Temporarily HTTP 302 error string Moved Permanently HTTP 301 error string Error connecting to %1 Unhandled HTTP error code %1 Profile received. Followers Following Profile updated. Comment %1 posted successfully. %1 is a piece of the comment Avatar published successfully. 1 comment received. %1 comments received. Post by %1 shared successfully. 1=author of the post we are sharing Adding items... Following %1 (%2) successfully. %1 is a person's name, %2 is the ID Stopped following %1 (%2) successfully. %1 is a person's name, %2 is the ID List of 'following' completely received. Partial list of 'following' received. List of 'followers' completely received. Partial list of 'followers' received. Person list deleted successfully. Person list received. File downloaded successfully. File uploaded successfully. Posting message... OAuth error while authorizing application. Authorized to use account %1. Getting initial data. There is no authorized account. Getting list of 'Following'... Getting list of 'Followers'... Getting site users for %1... %1 is a server name Getting list of person lists... The comments for this post cannot be loaded due to missing data on the server. Getting '%1'... %1 is the name of a feed Timeline Messages User timeline E-mail updated: %1 %1 published successfully. Updating post content... %1 is the type of object: note, image... Untitled post %1 published successfully. %1 is a piece of the post Post %1 published successfully. %1 is the title of the post Untitled post %1 updated successfully. %1 is a piece of the post Post %1 updated successfully. %1 is the title of the post Comment %1 updated successfully. %1 is a piece of the comment Message liked or unliked successfully. Likes received. Received '%1'. %1 is the name of a feed Message deleted successfully. List of 'lists' received. List of %1 users received. %1 is a server name Person list '%1' created successfully. %1 (%2) added to list successfully. 1=contact name, 2=contact ID %1 (%2) removed from list successfully. 1=contact name, 2=contact ID Group %1 created successfully. Group %1 joined successfully. Left the %1 group successfully. Avatar uploaded. SSL errors in connection to %1! Loading external image from %1 regardless of SSL errors, as configured... %1 is a hostname The application is not registered with your server yet. Registering... Getting OAuth token... OAuth support error Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support. You probably need to install the OpenSSL plugin for QCA: %1, %2 or similar. Authorization error There was an OAuth error while trying to get the authorization token. QOAuth error %1 Application authorized successfully. Waiting for proxy password... Still waiting for profile. Trying again... %1 attempts 1 attempt Some initial data was not received. Restarting initialization... Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally. All initial data received. Initialization complete. Ready. SiteUsersList You can get a list of the newest users registered on your server by clicking the button below. More resources to find users: Wiki page 'Users by language' PPump user search service at inventati.org Get list of users from your server Close list Loading... %1 users in %2 %1 = user count, %2 = server name TimeLine Welcome to Dianara Dianara is a <b>Pump.io</b> client. If you don't have a Pump account yet, you can get one at the following address, for instance: Press <b>F1</b> if you want to open the Help window. First, configure your account from the <b>Settings - Account</b> menu. After the process is done, your profile and timelines should update automatically. Take a moment to look around the menus and the Configuration window. You can also set your profile data and picture from the <b>Settings - Edit Profile</b> menu. There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information. Dianara's blog Pump.io User Guide Direct Messages Timeline Here, you'll see posts specifically directed to you. Activity Timeline You'll see your own posts here. Favorites Timeline Posts and comments you've liked. Newest Newer Older Requesting... Loading... Page %1 of %2. Showing %1 posts per page. %1 posts in total. Click here or press Control+G to jump to a specific page '%1' cannot be updated because a comment is currently being composed. %1 = feed's name %1 more posts pending for next update. Click here to receive them now. There are no posts Timestamp Invalid timestamp! A minute ago %1 minutes ago An hour ago %1 hours ago Just now In the future Yesterday %1 days ago A month ago %1 months ago A year ago %1 years ago UserPosts Posts by %1 Loading... &Close Received '%1'. %1 posts Error loading the timeline dianara-v1.3.2/translations/dianara_gl.ts000644 000764 000764 00000553476 12614520347 020076 0ustar00janjan000000 000000 ASActivity Public %1 by %2 1=kind of object: note, comment, etc; 2=author's name ASObject Note Noun, an object type Article Noun, an object type Image Noun, an object type Audio Noun, an object type Video Noun, an object type File Noun, an object type Comment Noun, as in object type: a comment Group Noun, an object type Collection Noun, an object type Other As in: other type of post No detailed location Deleted on %1 and one other and %1 others ASPerson Hometown AccountDialog Your Pump.io address: Get &Verifier Code Verifier code: Enter or paste the verifier code provided by your Pump server here &Save Details If the browser doesn't open automatically, copy this address manually Account Configuration First, enter your Webfinger ID, your pump.io address. Your address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. If your profile is at https://pump.example/yourname, then your address is yourname@pump.example If you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. 1=link to website If you need help: %1 Pump.io User Guide Your address, like username@pumpserver.org After clicking this button, a web browser will open, requesting authorization for Dianara Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. Don't translate the VERIFIER word! &Authorize Application &Cancel Your account is properly configured. Press Unlock if you wish to configure a different account. &Unlock A web browser will start now, where you can get the verifier code Your Pump address is invalid Verifier code is empty Dianara is authorized to access your data AudienceSelector 'To' List 'Cc' List &Add to Selected All Contacts Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. ON THE LEFT should change to ON THE RIGHT in RTL languages Clear &List &Done &Cancel Selected People AvatarButton Open %1's profile in web browser Open your profile in web browser Send message to %1 Browse messages Stop following Follow Stop following? Are you sure you want to stop following %1? &Yes, stop following &No ColorPicker Change Choose a color Comment Posted on %1 Modified on %1 Like or unlike this comment Quote This is a verb, infinitive Reply quoting this comment Edit Modify this comment Delete Erase this comment Unlike Like %1 like this comment Plural: %1=list of people like John, Jane, Smith %1 likes this comment Singular: %1=name of just 1 person WARNING: Delete comment? Are you sure you want to delete this comment? &Yes, delete it &No CommenterBlock You can press Control+Enter to send the comment with the keyboard Reload comments Comment Infinitive verb Cancel Press ESC to cancel the comment if there is no text Check for comments Show all %1 comments Comments are not available Error: Already composing You can't edit a comment at this time, because another comment is already being composed. Editing comment Posting comment failed. Try again. Sending comment... Updating comment... Comment is empty. Composer Type a message here to post it Click here or press Control+N to post a note... Symbols Formatting Normal Bold Italic Underline Strikethrough Header List Table Preformatted block Quote block Make a link Insert an image from a web site Insert line &Format Button for text formatting and related options Text Formatting Options Paste Text Without Formatting Type a comment here Insert as image? The link you are pasting seems to point to an image. Insert as visible image Insert as link Table Size How many rows (height)? How many columns (width)? Insert a link Type or paste a web address here. You could also select some text first, to turn it into a link. Make a link from selected text Type or paste a web address here. The selected text (%1) will be converted to a link. Insert an image from a URL Type or paste the image address here. The link must point to the image file directly. Error: Invalid URL The address you entered (%1) is not valid. Image addresses should begin with http:// or https:// Cancel message? Are you sure you want to cancel this message? &Yes, cancel it &No ConfigDialog minutes Top Bottom Program Configuration Timeline &update interval posts Goes after a number, as: 25 posts &Posts per page, main timeline posts This goes after a number, like: 10 posts Posts per page, &other timelines &Tabs position &Movable tabs Public posts as &default Pro&xy Settings Network configuration Set Up F&ilters Filtering rules Highlighted activities, except mine Any highlighted activity Always Never Comments Left side tabs on left side/west; RTL not affected Right side tabs on right side/east; RTL not affected You are among the recipients of the activity, such as a comment addressed to you. Used also when highlighting posts addressed to you in the timelines. The activity is in reply to something done by you, such as a comment posted in reply to one of your notes. You are the object of the activity, such as someone adding you to a list. The activity is related to one of your objects, such as someone liking one of your posts. Used also when highlighting your own posts in the timelines. Item highlighted due to filtering rules. Item is new. Show snippets in minor feeds Show information for deleted posts Hide duplicated posts Jump to new posts line on update Avatar size Show extended share information Show extra information Highlight post author's comments Highlight your own comments Ignore SSL errors in images Show character counter Don't inform followers when following someone Don't inform followers when handling lists As system notifications Using own notifications Don't show notifications Notification Style Notify when receiving: New posts Highlighted posts New activities in minor feed Highlighted activities in minor feed Default System iconset, if available Show your current avatar Custom icon System Tray Icon &Type S&elect... Custom &Icon Hide window on startup Timelines Posts Composer Privacy Notifications System Tray This is a system notification System notifications are not available! Own notifications will be used. This is a basic notification Select custom icon Post Titles characters This is a suffix, after a number Snippet limit Post Contents Minor Feeds Only for images inserted from web sites. Use with care. Use attachment filename as initial post title Inform only the author when liking things General Options Fonts Colors Dianara stores data in this folder: &Save Configuration &Cancel Image files All files Invalid image The selected image is not valid. ContactCard Hometown Joined: %1 Updated: %1 Bio for %1 Abbreviation for Biography, but you can use the full word; %1=contact name This user doesn't have a biography No biography for %1 %1=contact name Open Profile in Web Browser Send Message Browse Messages In Lists... User Options Follow Stop Following Stop following? Are you sure you want to stop following %1? &Yes, stop following &No ContactList Type a partial name or ID to find a contact... F&ull List ContactManager username@server.org or https://server.org/username &Enter address to follow: &Follow Reload Followers Reload Following Export Followers Export Following Reload Lists &Neighbors Follo&wers Followin&g &Lists Export list of 'following' to a file Export list of 'followers' to a file DownloadWidget Download Save the attached file to your folders Cancel Save File As... All files Abort download? Do you want to stop downloading the attached file? &Yes, stop &No, continue Download aborted Download completed Download failed Downloading %1 KiB... %1 KiB downloaded EmailChanger Change E-mail Address Change &Cancel E-mail Address: Again: Your Password: E-mail addresses don't match! Password is empty! FilterEditor Filter Editor %1 if %2 contains: %3 This explains a filter rule, like: Hide if Author ID contains JohnDoe Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. Hide Highlight Post Contents Author ID Application Activity Description Keywords... &Add Filter Filters in use &Remove Selected Filter &Save Filters &Cancel if contains &New Filter C&urrent Filters FirstRunWizard Welcome Wizard Welcome to Dianara! This wizard will help you get started. You can access this window again at any time from the Help menu. The first step is setting up your account, by using the following button: Configure your &account Once you have configured your account, it's recommended that you edit your profile and add an avatar and some other information, if you haven't done so already. &Edit your profile By default, Dianara will post only to your followers, but it's recommended that you post to Public, at least sometimes. Post to &Public by default Open general program &help window &Show this again next time Dianara starts &Close FontPicker Change Choose a font HelpWidget Basic Help Getting started The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. At this point, your profile, contact lists and timelines will be loaded. You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. Settings You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. Timelines Contents Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. If you're new to Pump.io, take a look at this guide: Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. The main timeline, where you'll see all the stuff posted or shared by the people you follow. Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. Activity timeline, where you'll see your own posts, or posts shared by you. Favorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. Posting New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. It is possible to attach images, audio, video, and general files, like PDF documents, to your post. You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. If you add a specific person to the 'To' list, they will receive your message in their direct messages tab. Choose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. Managing contacts You can see the lists of people you follow, and who follow you from the Contacts tab. There, you can also manage person lists, used mainly to send posts to specific groups of people. You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. Keyboard controls Pump.io User Guide There are seven timelines: The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. LEFT SIDE should change to RIGHT SIDE on RTL languages The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). You can select who will see your post by using the To and Cc buttons. You can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. There is a text field at the top, where you can directly enter addresses of new contacts to follow them. You can also send a direct message (initially private) to that contact from this menu. You can find a list with some Pump.io users and other information here: Users by language The most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. Besides that, you can use: Control+Up/Down/PgUp/PgDown/Home/End to move around the timeline. Control+Left/Right to jump one page in the timeline. Control+G to go to any page in the timeline directly. Control+1/2/3 to switch between the minor feeds. Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. Dianara offers a D-Bus interface that allows some control from other applications. The interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. Command line options Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. Use the --debug parameter to have extra information in your terminal window, about what the program is doing. If your server does not support HTTPS, you can use the --nohttps parameter. If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. &Close ImageViewer Image &Save As... &Restart Animation &Close Save Image... Close Viewer Save Image As... Image files All files Error saving image There was a problem while saving %1. Filename should end in .jpg or .png extensions. ListsManager Name Members Add Mem&ber &Remove Member &Delete Selected List Add New &List Create L&ist &Add to List Are you sure you want to delete %1? 1=Name of a person list Remove person from list? Are you sure you want to remove %1 from the %2 list? 1=Name of a person, 2=name of a list &Yes Type a name for the new list... Type an optional description here WARNING: Delete list? &Yes, delete it &No LogViewer Log Clear &Log &Close MainWindow Side &Panel Status &Bar &Timeline The main timeline &Messages Messages sent explicitly to you &Activity Your own posts Your favorited posts &Contacts Initializing... Your account is not configured yet. Dianara started. Minor activities done by everyone, such as replying to posts Minor activities addressed to you Minor activities done by you The people you follow, the ones who follow you, and your person lists Press F1 for help Running with Qt v%1. Click here to configure your account &Session Auto-update &Timelines Mark All as Read &Post a Note &Quit &View &Toolbar Full &Screen &Log S&ettings Edit &Profile &Account Basic &Help Report a &Bug Pump.io User &Guide Some Pump.io &Tips List of Some Pump.io &Users Pump.io &Network Status Website Toolbar Open the log viewer Auto-updating enabled Auto-updating disabled Proxy password required You have configured a proxy server with authentication, but the password is not set. Enter the password for your proxy server: Starting automatic update of timelines, once every %1 minutes. Stopping automatic update of timelines. Received %1 older posts in '%2'. %1 is a number, %2 = name of a timeline 1 more pending to receive. singular, one post %1 more pending to receive. plural, several posts Also: Last update: %1 '%1' updated. %1 is the name of a feed Show Welcome Wizard 1 filtered out. singular, refers to a post %1 filtered out. plural, refers to posts 1 deleted. singular, refers to a post %1 deleted. plural, refers to posts There is 1 new activity. There are %1 new activities. 1 highlighted. singular, refers to an activity %1 highlighted. plural, refers to activities 1 filtered out. singular, refers to one activity %1 filtered out. plural, several activities No new activities. Error storing image! %1 bytes Marking everything as read... Closing due to environment shutting down... Quit? You are composing a note or a comment. Do you really want to close Dianara? &Yes, close the program &No Shutting down Dianara... System tray icon is not available. Dianara cannot be hidden in the system tray. Do you want to close the program completely? Timeline updated at %1. Update %1 Your Pump.io account is not configured Link to: %1 With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. English translation by JanKusanagi. TRANSLATORS: Change this with your language and name. If there was another translator before you, add your name after theirs ;) Dianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) &Configure Dianara &Filters and Highlighting &Help Visit &Website About &Dianara Your biography is empty Click to edit your profile 1 highlighted. singular, refers to a post %1 highlighted. plural, refers to posts No new posts. Total posts: %1 Favor&ites Received %1 older activities in '%2'. %1 is a number, %2 = name of feed Minor feed updated at %1. 1 more pending to receive. singular, 1 activity %1 more pending to receive. plural, several activities &Hide Window &Show Window There is 1 new post. There are %1 new posts. About Dianara Dianara is a pump.io social networking client. Thanks to all the testers, translators and packagers, who help make Dianara better! MinorFeed Older Activities Get previous minor activities There are no activities to show yet. Get %1 newer As in: Get 3 newer (activities) MinorFeedItem Using %1 Application used to generate this activity To: %1 1=people to whom this activity was sent Cc: %1 1=people to whom this activity was sent as CC Open referenced post MiscHelpers bytes PageSelector Jump to page Page number: &First As in: first page &Last As in: last page Newer As in: newer pages Older As in: older pages &Go &Cancel PeopleWidget &Search: Enter a name here to search for it Add a contact to a list &Cancel Post Via %1 Shared on %1 Loading image... Edited: %1 Posted on %1 1=Date In To Post Noun, not verb Using %1 1=Program used for posting or sharing If you select some text, it will be quoted. Share Unshare Unshare this post Edit Delete Open post in web browser Click to download the attachment Cc Copy post link to clipboard Normalize text colors &Close Type As in: type of object Modified on %1 Parent As in 'Open the parent post'. Try to use the shortest word! Open the parent post, to which this one replies Comment verb, for the comment button Reply to this post. Share this post with your contacts Modify this post Erase this post Join Group %1 members in the group Image is animated. Click on it to play. Couldn't load image! Attached Audio Attached Video Attached File %1 likes this One person %1 like this More than one person 1 like %1 likes 1 comment %1 comments %1 shared this %1 = One person name %1 shared this %1 = Names for more than one person Shared once Shared %1 times You like this Unlike Like this post Like Share post? Do you want to share %1's post? &Yes, share it &No Unshare post? Do you want to unshare %1's post? &Yes, unshare it WARNING: Delete post? Are you sure you want to delete this post? &Yes, delete it Click the image to see it in full size ProfileEditor Profile Editor This is your Pump address This is the e-mail address associated with your account, for things such as notifications and password recovery Change &E-mail... Change &Avatar... This is your visible name &Save Profile &Cancel Webfinger ID E-mail Avatar Full &Name &Hometown &Bio Not set In reference to the e-mail not being set for the account Select avatar image Image files All files Invalid image The selected image is not valid. ProxyDialog Proxy Configuration Do not use a proxy Your proxy username Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. &Save &Cancel Proxy &Type &Hostname &Port Use &Authentication &User Pass&word Publisher Select Picture... Find the picture in your folders Public Followers People... Select who will see this post To... Setting a title helps make the Meanwhile feed more informative Title Remove Cancel the attachment, and go back to a regular note Lists Select who will get a copy of this post Picture Audio Video Other as in other kinds of files Ad&d... Upload media, like pictures or videos Hit Control+Enter to post with the keyboard Cancel Cancel the post Picture not set Select Audio File... Find the audio file in your folders Audio file not set Select Video... Find the video in your folders Video not set Select File... Find the file in your folders File not set Error: Already composing You can't edit a post at this time, because a post is already being composed. Update Editing post You can't create a message for %1 at this time, because a post is already being composed. Posting failed. Try again. Warning: You have no followers yet You're trying to post to your followers only, but you don't have any followers yet. If you post like this, no one will be able to see your message. Do you want to make the post public instead of followers-only? &Yes, make it public &Cancel, go back to the post Updating... Post is empty. File not selected. Select one image Image files Select one file Invalid file The file type cannot be detected. All files Since you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. File is too big Dianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. This is a temporary measure, since the servers cannot set their own limits yet. Sorry for the inconvenience. Resolution Type Size %1 KiB of %2 KiB uploaded Invalid image Add a brief title for the post here (recommended) Cc... Post verb Note started from another application. Ignoring new note request from another application. &No, post to my followers only The image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. Select one audio file Audio files Invalid audio file The audio format cannot be detected. Select one video file Video files Invalid video file The video format cannot be detected. Posting... PumpController Creating person list... Deleting person list... Getting likes... Getting comments... Error connecting to %1 Unhandled HTTP error code %1 Message liked or unliked successfully. Comment %1 posted successfully. %1 is a piece of the comment Message deleted successfully. Likes received. Authorized to use account %1. Getting initial data. There is no authorized account. Getting list of 'Following'... Getting list of 'Followers'... Getting site users for %1... %1 is a server name Getting list of person lists... Getting a person list... Adding person to list... Removing person from list... Creating group... Joining group... Leaving group... Getting '%1'... %1 is the name of a feed Timeline Messages User timeline Uploading %1 1=filename HTTP error For the following HTTP error codesyou can check http://en.wikipedia.org/wiki/List_of_HTTP_status_codes in your language Gateway Timeout HTTP 504 error string Service Unavailable HTTP 503 error string Not Implemented HTTP 501 error string Internal Server Error HTTP 500 error string Gone HTTP 410 error string Not Found HTTP 404 error string Forbidden HTTP 403 error string Unauthorized HTTP 401 error string Bad Request HTTP 400 error string Moved Temporarily HTTP 302 error string Moved Permanently HTTP 301 error string Profile received. Followers Following Profile updated. E-mail updated: %1 %1 published successfully. Updating post content... %1 is the type of object: note, image... Untitled post %1 published successfully. %1 is a piece of the post Post %1 published successfully. %1 is the title of the post Avatar published successfully. Untitled post %1 updated successfully. %1 is a piece of the post Post %1 updated successfully. %1 is the title of the post Comment %1 updated successfully. %1 is a piece of the comment Adding items... Following %1 (%2) successfully. %1 is a person's name, %2 is the ID Stopped following %1 (%2) successfully. %1 is a person's name, %2 is the ID List of %1 users received. %1 is a server name Person list '%1' created successfully. %1 (%2) added to list successfully. 1=contact name, 2=contact ID %1 (%2) removed from list successfully. 1=contact name, 2=contact ID Group %1 created successfully. Group %1 joined successfully. Left the %1 group successfully. OAuth error while authorizing application. %1 attempts 1 attempt Some initial data was not received. Restarting initialization... List of 'following' completely received. The comments for this post cannot be loaded due to missing data on the server. Activity Favorites Meanwhile Mentions Actions 1 comment received. %1 comments received. Post by %1 shared successfully. 1=author of the post we are sharing Received '%1'. %1 is the name of a feed Partial list of 'following' received. List of 'followers' completely received. Partial list of 'followers' received. List of 'lists' received. Person list deleted successfully. Person list received. File downloaded successfully. File uploaded successfully. Posting message... Avatar uploaded. SSL errors in connection to %1! Loading external image from %1 regardless of SSL errors, as configured... %1 is a hostname The application is not registered with your server yet. Registering... Getting OAuth token... OAuth support error Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support. You probably need to install the OpenSSL plugin for QCA: %1, %2 or similar. Authorization error There was an OAuth error while trying to get the authorization token. QOAuth error %1 Application authorized successfully. Waiting for proxy password... Still waiting for profile. Trying again... Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally. All initial data received. Initialization complete. Ready. SiteUsersList You can get a list of the newest users registered on your server by clicking the button below. More resources to find users: Wiki page 'Users by language' PPump user search service at inventati.org Get list of users from your server Close list Loading... %1 users in %2 %1 = user count, %2 = server name TimeLine Welcome to Dianara Dianara is a <b>Pump.io</b> client. If you don't have a Pump account yet, you can get one at the following address, for instance: Press <b>F1</b> if you want to open the Help window. First, configure your account from the <b>Settings - Account</b> menu. After the process is done, your profile and timelines should update automatically. Take a moment to look around the menus and the Configuration window. You can also set your profile data and picture from the <b>Settings - Edit Profile</b> menu. There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information. Dianara's blog Pump.io User Guide Direct Messages Timeline Here, you'll see posts specifically directed to you. Activity Timeline You'll see your own posts here. Favorites Timeline Posts and comments you've liked. Newest Newer Older Requesting... Loading... Page %1 of %2. Showing %1 posts per page. %1 posts in total. Click here or press Control+G to jump to a specific page '%1' cannot be updated because a comment is currently being composed. %1 = feed's name %1 more posts pending for next update. Click here to receive them now. There are no posts Timestamp Invalid timestamp! A minute ago %1 minutes ago An hour ago %1 hours ago Just now In the future Yesterday %1 days ago A month ago %1 months ago A year ago %1 years ago UserPosts Posts by %1 Loading... &Close Received '%1'. %1 posts Error loading the timeline dianara-v1.3.2/translations/dianara_eu.ts000644 000764 000764 00000553476 12614520347 020105 0ustar00janjan000000 000000 ASActivity Public %1 by %2 1=kind of object: note, comment, etc; 2=author's name ASObject Note Noun, an object type Article Noun, an object type Image Noun, an object type Audio Noun, an object type Video Noun, an object type File Noun, an object type Comment Noun, as in object type: a comment Group Noun, an object type Collection Noun, an object type Other As in: other type of post No detailed location Deleted on %1 and one other and %1 others ASPerson Hometown AccountDialog Your Pump.io address: Get &Verifier Code Verifier code: Enter or paste the verifier code provided by your Pump server here &Save Details If the browser doesn't open automatically, copy this address manually Account Configuration First, enter your Webfinger ID, your pump.io address. Your address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. If your profile is at https://pump.example/yourname, then your address is yourname@pump.example If you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. 1=link to website If you need help: %1 Pump.io User Guide Your address, like username@pumpserver.org After clicking this button, a web browser will open, requesting authorization for Dianara Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. Don't translate the VERIFIER word! &Authorize Application &Cancel Your account is properly configured. Press Unlock if you wish to configure a different account. &Unlock A web browser will start now, where you can get the verifier code Your Pump address is invalid Verifier code is empty Dianara is authorized to access your data AudienceSelector 'To' List 'Cc' List &Add to Selected All Contacts Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. ON THE LEFT should change to ON THE RIGHT in RTL languages Clear &List &Done &Cancel Selected People AvatarButton Open %1's profile in web browser Open your profile in web browser Send message to %1 Browse messages Stop following Follow Stop following? Are you sure you want to stop following %1? &Yes, stop following &No ColorPicker Change Choose a color Comment Posted on %1 Modified on %1 Like or unlike this comment Quote This is a verb, infinitive Reply quoting this comment Edit Modify this comment Delete Erase this comment Unlike Like %1 like this comment Plural: %1=list of people like John, Jane, Smith %1 likes this comment Singular: %1=name of just 1 person WARNING: Delete comment? Are you sure you want to delete this comment? &Yes, delete it &No CommenterBlock You can press Control+Enter to send the comment with the keyboard Reload comments Comment Infinitive verb Cancel Press ESC to cancel the comment if there is no text Check for comments Show all %1 comments Comments are not available Error: Already composing You can't edit a comment at this time, because another comment is already being composed. Editing comment Posting comment failed. Try again. Sending comment... Updating comment... Comment is empty. Composer Type a message here to post it Click here or press Control+N to post a note... Symbols Formatting Normal Bold Italic Underline Strikethrough Header List Table Preformatted block Quote block Make a link Insert an image from a web site Insert line &Format Button for text formatting and related options Text Formatting Options Paste Text Without Formatting Type a comment here Insert as image? The link you are pasting seems to point to an image. Insert as visible image Insert as link Table Size How many rows (height)? How many columns (width)? Insert a link Type or paste a web address here. You could also select some text first, to turn it into a link. Make a link from selected text Type or paste a web address here. The selected text (%1) will be converted to a link. Insert an image from a URL Type or paste the image address here. The link must point to the image file directly. Error: Invalid URL The address you entered (%1) is not valid. Image addresses should begin with http:// or https:// Cancel message? Are you sure you want to cancel this message? &Yes, cancel it &No ConfigDialog minutes Top Bottom Program Configuration Timeline &update interval posts Goes after a number, as: 25 posts &Posts per page, main timeline posts This goes after a number, like: 10 posts Posts per page, &other timelines &Tabs position &Movable tabs Public posts as &default Pro&xy Settings Network configuration Set Up F&ilters Filtering rules Highlighted activities, except mine Any highlighted activity Always Never Comments Left side tabs on left side/west; RTL not affected Right side tabs on right side/east; RTL not affected You are among the recipients of the activity, such as a comment addressed to you. Used also when highlighting posts addressed to you in the timelines. The activity is in reply to something done by you, such as a comment posted in reply to one of your notes. You are the object of the activity, such as someone adding you to a list. The activity is related to one of your objects, such as someone liking one of your posts. Used also when highlighting your own posts in the timelines. Item highlighted due to filtering rules. Item is new. Show snippets in minor feeds Show information for deleted posts Hide duplicated posts Jump to new posts line on update Avatar size Show extended share information Show extra information Highlight post author's comments Highlight your own comments Ignore SSL errors in images Show character counter Don't inform followers when following someone Don't inform followers when handling lists As system notifications Using own notifications Don't show notifications Notification Style Notify when receiving: New posts Highlighted posts New activities in minor feed Highlighted activities in minor feed Default System iconset, if available Show your current avatar Custom icon System Tray Icon &Type S&elect... Custom &Icon Hide window on startup Timelines Posts Composer Privacy Notifications System Tray This is a system notification System notifications are not available! Own notifications will be used. This is a basic notification Select custom icon Post Titles characters This is a suffix, after a number Snippet limit Post Contents Minor Feeds Only for images inserted from web sites. Use with care. Use attachment filename as initial post title Inform only the author when liking things General Options Fonts Colors Dianara stores data in this folder: &Save Configuration &Cancel Image files All files Invalid image The selected image is not valid. ContactCard Hometown Joined: %1 Updated: %1 Bio for %1 Abbreviation for Biography, but you can use the full word; %1=contact name This user doesn't have a biography No biography for %1 %1=contact name Open Profile in Web Browser Send Message Browse Messages In Lists... User Options Follow Stop Following Stop following? Are you sure you want to stop following %1? &Yes, stop following &No ContactList Type a partial name or ID to find a contact... F&ull List ContactManager username@server.org or https://server.org/username &Enter address to follow: &Follow Reload Followers Reload Following Export Followers Export Following Reload Lists &Neighbors Follo&wers Followin&g &Lists Export list of 'following' to a file Export list of 'followers' to a file DownloadWidget Download Save the attached file to your folders Cancel Save File As... All files Abort download? Do you want to stop downloading the attached file? &Yes, stop &No, continue Download aborted Download completed Download failed Downloading %1 KiB... %1 KiB downloaded EmailChanger Change E-mail Address Change &Cancel E-mail Address: Again: Your Password: E-mail addresses don't match! Password is empty! FilterEditor Filter Editor %1 if %2 contains: %3 This explains a filter rule, like: Hide if Author ID contains JohnDoe Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. Hide Highlight Post Contents Author ID Application Activity Description Keywords... &Add Filter Filters in use &Remove Selected Filter &Save Filters &Cancel if contains &New Filter C&urrent Filters FirstRunWizard Welcome Wizard Welcome to Dianara! This wizard will help you get started. You can access this window again at any time from the Help menu. The first step is setting up your account, by using the following button: Configure your &account Once you have configured your account, it's recommended that you edit your profile and add an avatar and some other information, if you haven't done so already. &Edit your profile By default, Dianara will post only to your followers, but it's recommended that you post to Public, at least sometimes. Post to &Public by default Open general program &help window &Show this again next time Dianara starts &Close FontPicker Change Choose a font HelpWidget Basic Help Getting started The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. At this point, your profile, contact lists and timelines will be loaded. You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. Settings You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. Timelines Contents Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. If you're new to Pump.io, take a look at this guide: Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. The main timeline, where you'll see all the stuff posted or shared by the people you follow. Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. Activity timeline, where you'll see your own posts, or posts shared by you. Favorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. Posting New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. It is possible to attach images, audio, video, and general files, like PDF documents, to your post. You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. If you add a specific person to the 'To' list, they will receive your message in their direct messages tab. Choose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. Managing contacts You can see the lists of people you follow, and who follow you from the Contacts tab. There, you can also manage person lists, used mainly to send posts to specific groups of people. You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. Keyboard controls Pump.io User Guide There are seven timelines: The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. LEFT SIDE should change to RIGHT SIDE on RTL languages The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). You can select who will see your post by using the To and Cc buttons. You can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. There is a text field at the top, where you can directly enter addresses of new contacts to follow them. You can also send a direct message (initially private) to that contact from this menu. You can find a list with some Pump.io users and other information here: Users by language The most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. Besides that, you can use: Control+Up/Down/PgUp/PgDown/Home/End to move around the timeline. Control+Left/Right to jump one page in the timeline. Control+G to go to any page in the timeline directly. Control+1/2/3 to switch between the minor feeds. Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. Dianara offers a D-Bus interface that allows some control from other applications. The interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. Command line options Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. Use the --debug parameter to have extra information in your terminal window, about what the program is doing. If your server does not support HTTPS, you can use the --nohttps parameter. If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. &Close ImageViewer Image &Save As... &Restart Animation &Close Save Image... Close Viewer Save Image As... Image files All files Error saving image There was a problem while saving %1. Filename should end in .jpg or .png extensions. ListsManager Name Members Add Mem&ber &Remove Member &Delete Selected List Add New &List Create L&ist &Add to List Are you sure you want to delete %1? 1=Name of a person list Remove person from list? Are you sure you want to remove %1 from the %2 list? 1=Name of a person, 2=name of a list &Yes Type a name for the new list... Type an optional description here WARNING: Delete list? &Yes, delete it &No LogViewer Log Clear &Log &Close MainWindow Side &Panel Status &Bar &Timeline The main timeline &Messages Messages sent explicitly to you &Activity Your own posts Your favorited posts &Contacts Initializing... Your account is not configured yet. Dianara started. Minor activities done by everyone, such as replying to posts Minor activities addressed to you Minor activities done by you The people you follow, the ones who follow you, and your person lists Press F1 for help Running with Qt v%1. Click here to configure your account &Session Auto-update &Timelines Mark All as Read &Post a Note &Quit &View &Toolbar Full &Screen &Log S&ettings Edit &Profile &Account Basic &Help Report a &Bug Pump.io User &Guide Some Pump.io &Tips List of Some Pump.io &Users Pump.io &Network Status Website Toolbar Open the log viewer Auto-updating enabled Auto-updating disabled Proxy password required You have configured a proxy server with authentication, but the password is not set. Enter the password for your proxy server: Starting automatic update of timelines, once every %1 minutes. Stopping automatic update of timelines. Received %1 older posts in '%2'. %1 is a number, %2 = name of a timeline 1 more pending to receive. singular, one post %1 more pending to receive. plural, several posts Also: Last update: %1 '%1' updated. %1 is the name of a feed Show Welcome Wizard 1 filtered out. singular, refers to a post %1 filtered out. plural, refers to posts 1 deleted. singular, refers to a post %1 deleted. plural, refers to posts There is 1 new activity. There are %1 new activities. 1 highlighted. singular, refers to an activity %1 highlighted. plural, refers to activities 1 filtered out. singular, refers to one activity %1 filtered out. plural, several activities No new activities. Error storing image! %1 bytes Marking everything as read... Closing due to environment shutting down... Quit? You are composing a note or a comment. Do you really want to close Dianara? &Yes, close the program &No Shutting down Dianara... System tray icon is not available. Dianara cannot be hidden in the system tray. Do you want to close the program completely? Timeline updated at %1. Update %1 Your Pump.io account is not configured Link to: %1 With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. English translation by JanKusanagi. TRANSLATORS: Change this with your language and name. If there was another translator before you, add your name after theirs ;) Dianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) &Configure Dianara &Filters and Highlighting &Help Visit &Website About &Dianara Your biography is empty Click to edit your profile 1 highlighted. singular, refers to a post %1 highlighted. plural, refers to posts No new posts. Total posts: %1 Favor&ites Received %1 older activities in '%2'. %1 is a number, %2 = name of feed Minor feed updated at %1. 1 more pending to receive. singular, 1 activity %1 more pending to receive. plural, several activities &Hide Window &Show Window There is 1 new post. There are %1 new posts. About Dianara Dianara is a pump.io social networking client. Thanks to all the testers, translators and packagers, who help make Dianara better! MinorFeed Older Activities Get previous minor activities There are no activities to show yet. Get %1 newer As in: Get 3 newer (activities) MinorFeedItem Using %1 Application used to generate this activity To: %1 1=people to whom this activity was sent Cc: %1 1=people to whom this activity was sent as CC Open referenced post MiscHelpers bytes PageSelector Jump to page Page number: &First As in: first page &Last As in: last page Newer As in: newer pages Older As in: older pages &Go &Cancel PeopleWidget &Search: Enter a name here to search for it Add a contact to a list &Cancel Post Via %1 Shared on %1 Loading image... Edited: %1 Posted on %1 1=Date In To Post Noun, not verb Using %1 1=Program used for posting or sharing If you select some text, it will be quoted. Share Unshare Unshare this post Edit Delete Open post in web browser Click to download the attachment Cc Copy post link to clipboard Normalize text colors &Close Type As in: type of object Modified on %1 Parent As in 'Open the parent post'. Try to use the shortest word! Open the parent post, to which this one replies Comment verb, for the comment button Reply to this post. Share this post with your contacts Modify this post Erase this post Join Group %1 members in the group Image is animated. Click on it to play. Couldn't load image! Attached Audio Attached Video Attached File %1 likes this One person %1 like this More than one person 1 like %1 likes 1 comment %1 comments %1 shared this %1 = One person name %1 shared this %1 = Names for more than one person Shared once Shared %1 times You like this Unlike Like this post Like Share post? Do you want to share %1's post? &Yes, share it &No Unshare post? Do you want to unshare %1's post? &Yes, unshare it WARNING: Delete post? Are you sure you want to delete this post? &Yes, delete it Click the image to see it in full size ProfileEditor Profile Editor This is your Pump address This is the e-mail address associated with your account, for things such as notifications and password recovery Change &E-mail... Change &Avatar... This is your visible name &Save Profile &Cancel Webfinger ID E-mail Avatar Full &Name &Hometown &Bio Not set In reference to the e-mail not being set for the account Select avatar image Image files All files Invalid image The selected image is not valid. ProxyDialog Proxy Configuration Do not use a proxy Your proxy username Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. &Save &Cancel Proxy &Type &Hostname &Port Use &Authentication &User Pass&word Publisher Select Picture... Find the picture in your folders Public Followers People... Select who will see this post To... Setting a title helps make the Meanwhile feed more informative Title Remove Cancel the attachment, and go back to a regular note Lists Select who will get a copy of this post Picture Audio Video Other as in other kinds of files Ad&d... Upload media, like pictures or videos Hit Control+Enter to post with the keyboard Cancel Cancel the post Picture not set Select Audio File... Find the audio file in your folders Audio file not set Select Video... Find the video in your folders Video not set Select File... Find the file in your folders File not set Error: Already composing You can't edit a post at this time, because a post is already being composed. Update Editing post You can't create a message for %1 at this time, because a post is already being composed. Posting failed. Try again. Warning: You have no followers yet You're trying to post to your followers only, but you don't have any followers yet. If you post like this, no one will be able to see your message. Do you want to make the post public instead of followers-only? &Yes, make it public &Cancel, go back to the post Updating... Post is empty. File not selected. Select one image Image files Select one file Invalid file The file type cannot be detected. All files Since you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. File is too big Dianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. This is a temporary measure, since the servers cannot set their own limits yet. Sorry for the inconvenience. Resolution Type Size %1 KiB of %2 KiB uploaded Invalid image Add a brief title for the post here (recommended) Cc... Post verb Note started from another application. Ignoring new note request from another application. &No, post to my followers only The image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. Select one audio file Audio files Invalid audio file The audio format cannot be detected. Select one video file Video files Invalid video file The video format cannot be detected. Posting... PumpController Creating person list... Deleting person list... Getting likes... Getting comments... Error connecting to %1 Unhandled HTTP error code %1 Message liked or unliked successfully. Comment %1 posted successfully. %1 is a piece of the comment Message deleted successfully. Likes received. Authorized to use account %1. Getting initial data. There is no authorized account. Getting list of 'Following'... Getting list of 'Followers'... Getting site users for %1... %1 is a server name Getting list of person lists... Getting a person list... Adding person to list... Removing person from list... Creating group... Joining group... Leaving group... Getting '%1'... %1 is the name of a feed Timeline Messages User timeline Uploading %1 1=filename HTTP error For the following HTTP error codesyou can check http://en.wikipedia.org/wiki/List_of_HTTP_status_codes in your language Gateway Timeout HTTP 504 error string Service Unavailable HTTP 503 error string Not Implemented HTTP 501 error string Internal Server Error HTTP 500 error string Gone HTTP 410 error string Not Found HTTP 404 error string Forbidden HTTP 403 error string Unauthorized HTTP 401 error string Bad Request HTTP 400 error string Moved Temporarily HTTP 302 error string Moved Permanently HTTP 301 error string Profile received. Followers Following Profile updated. E-mail updated: %1 %1 published successfully. Updating post content... %1 is the type of object: note, image... Untitled post %1 published successfully. %1 is a piece of the post Post %1 published successfully. %1 is the title of the post Avatar published successfully. Untitled post %1 updated successfully. %1 is a piece of the post Post %1 updated successfully. %1 is the title of the post Comment %1 updated successfully. %1 is a piece of the comment Adding items... Following %1 (%2) successfully. %1 is a person's name, %2 is the ID Stopped following %1 (%2) successfully. %1 is a person's name, %2 is the ID List of %1 users received. %1 is a server name Person list '%1' created successfully. %1 (%2) added to list successfully. 1=contact name, 2=contact ID %1 (%2) removed from list successfully. 1=contact name, 2=contact ID Group %1 created successfully. Group %1 joined successfully. Left the %1 group successfully. OAuth error while authorizing application. %1 attempts 1 attempt Some initial data was not received. Restarting initialization... List of 'following' completely received. The comments for this post cannot be loaded due to missing data on the server. Activity Favorites Meanwhile Mentions Actions 1 comment received. %1 comments received. Post by %1 shared successfully. 1=author of the post we are sharing Received '%1'. %1 is the name of a feed Partial list of 'following' received. List of 'followers' completely received. Partial list of 'followers' received. List of 'lists' received. Person list deleted successfully. Person list received. File downloaded successfully. File uploaded successfully. Posting message... Avatar uploaded. SSL errors in connection to %1! Loading external image from %1 regardless of SSL errors, as configured... %1 is a hostname The application is not registered with your server yet. Registering... Getting OAuth token... OAuth support error Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support. You probably need to install the OpenSSL plugin for QCA: %1, %2 or similar. Authorization error There was an OAuth error while trying to get the authorization token. QOAuth error %1 Application authorized successfully. Waiting for proxy password... Still waiting for profile. Trying again... Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally. All initial data received. Initialization complete. Ready. SiteUsersList You can get a list of the newest users registered on your server by clicking the button below. More resources to find users: Wiki page 'Users by language' PPump user search service at inventati.org Get list of users from your server Close list Loading... %1 users in %2 %1 = user count, %2 = server name TimeLine Welcome to Dianara Dianara is a <b>Pump.io</b> client. If you don't have a Pump account yet, you can get one at the following address, for instance: Press <b>F1</b> if you want to open the Help window. First, configure your account from the <b>Settings - Account</b> menu. After the process is done, your profile and timelines should update automatically. Take a moment to look around the menus and the Configuration window. You can also set your profile data and picture from the <b>Settings - Edit Profile</b> menu. There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information. Dianara's blog Pump.io User Guide Direct Messages Timeline Here, you'll see posts specifically directed to you. Activity Timeline You'll see your own posts here. Favorites Timeline Posts and comments you've liked. Newest Newer Older Requesting... Loading... Page %1 of %2. Showing %1 posts per page. %1 posts in total. Click here or press Control+G to jump to a specific page '%1' cannot be updated because a comment is currently being composed. %1 = feed's name %1 more posts pending for next update. Click here to receive them now. There are no posts Timestamp Invalid timestamp! A minute ago %1 minutes ago An hour ago %1 hours ago Just now In the future Yesterday %1 days ago A month ago %1 months ago A year ago %1 years ago UserPosts Posts by %1 Loading... &Close Received '%1'. %1 posts Error loading the timeline dianara-v1.3.2/translations/dianara_pt.ts000644 000764 000764 00000553476 12614520350 020111 0ustar00janjan000000 000000 ASActivity Public %1 by %2 1=kind of object: note, comment, etc; 2=author's name ASObject Note Noun, an object type Article Noun, an object type Image Noun, an object type Audio Noun, an object type Video Noun, an object type File Noun, an object type Comment Noun, as in object type: a comment Group Noun, an object type Collection Noun, an object type Other As in: other type of post No detailed location Deleted on %1 and one other and %1 others ASPerson Hometown AccountDialog Your Pump.io address: Get &Verifier Code Verifier code: Enter or paste the verifier code provided by your Pump server here &Save Details If the browser doesn't open automatically, copy this address manually Account Configuration First, enter your Webfinger ID, your pump.io address. Your address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. If your profile is at https://pump.example/yourname, then your address is yourname@pump.example If you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. 1=link to website If you need help: %1 Pump.io User Guide Your address, like username@pumpserver.org After clicking this button, a web browser will open, requesting authorization for Dianara Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. Don't translate the VERIFIER word! &Authorize Application &Cancel Your account is properly configured. Press Unlock if you wish to configure a different account. &Unlock A web browser will start now, where you can get the verifier code Your Pump address is invalid Verifier code is empty Dianara is authorized to access your data AudienceSelector 'To' List 'Cc' List &Add to Selected All Contacts Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. ON THE LEFT should change to ON THE RIGHT in RTL languages Clear &List &Done &Cancel Selected People AvatarButton Open %1's profile in web browser Open your profile in web browser Send message to %1 Browse messages Stop following Follow Stop following? Are you sure you want to stop following %1? &Yes, stop following &No ColorPicker Change Choose a color Comment Posted on %1 Modified on %1 Like or unlike this comment Quote This is a verb, infinitive Reply quoting this comment Edit Modify this comment Delete Erase this comment Unlike Like %1 like this comment Plural: %1=list of people like John, Jane, Smith %1 likes this comment Singular: %1=name of just 1 person WARNING: Delete comment? Are you sure you want to delete this comment? &Yes, delete it &No CommenterBlock You can press Control+Enter to send the comment with the keyboard Reload comments Comment Infinitive verb Cancel Press ESC to cancel the comment if there is no text Check for comments Show all %1 comments Comments are not available Error: Already composing You can't edit a comment at this time, because another comment is already being composed. Editing comment Posting comment failed. Try again. Sending comment... Updating comment... Comment is empty. Composer Type a message here to post it Click here or press Control+N to post a note... Symbols Formatting Normal Bold Italic Underline Strikethrough Header List Table Preformatted block Quote block Make a link Insert an image from a web site Insert line &Format Button for text formatting and related options Text Formatting Options Paste Text Without Formatting Type a comment here Insert as image? The link you are pasting seems to point to an image. Insert as visible image Insert as link Table Size How many rows (height)? How many columns (width)? Insert a link Type or paste a web address here. You could also select some text first, to turn it into a link. Make a link from selected text Type or paste a web address here. The selected text (%1) will be converted to a link. Insert an image from a URL Type or paste the image address here. The link must point to the image file directly. Error: Invalid URL The address you entered (%1) is not valid. Image addresses should begin with http:// or https:// Cancel message? Are you sure you want to cancel this message? &Yes, cancel it &No ConfigDialog minutes Top Bottom Program Configuration Timeline &update interval posts Goes after a number, as: 25 posts &Posts per page, main timeline posts This goes after a number, like: 10 posts Posts per page, &other timelines &Tabs position &Movable tabs Public posts as &default Pro&xy Settings Network configuration Set Up F&ilters Filtering rules Highlighted activities, except mine Any highlighted activity Always Never Comments Left side tabs on left side/west; RTL not affected Right side tabs on right side/east; RTL not affected You are among the recipients of the activity, such as a comment addressed to you. Used also when highlighting posts addressed to you in the timelines. The activity is in reply to something done by you, such as a comment posted in reply to one of your notes. You are the object of the activity, such as someone adding you to a list. The activity is related to one of your objects, such as someone liking one of your posts. Used also when highlighting your own posts in the timelines. Item highlighted due to filtering rules. Item is new. Show snippets in minor feeds Show information for deleted posts Hide duplicated posts Jump to new posts line on update Avatar size Show extended share information Show extra information Highlight post author's comments Highlight your own comments Ignore SSL errors in images Show character counter Don't inform followers when following someone Don't inform followers when handling lists As system notifications Using own notifications Don't show notifications Notification Style Notify when receiving: New posts Highlighted posts New activities in minor feed Highlighted activities in minor feed Default System iconset, if available Show your current avatar Custom icon System Tray Icon &Type S&elect... Custom &Icon Hide window on startup Timelines Posts Composer Privacy Notifications System Tray This is a system notification System notifications are not available! Own notifications will be used. This is a basic notification Select custom icon Post Titles characters This is a suffix, after a number Snippet limit Post Contents Minor Feeds Only for images inserted from web sites. Use with care. Use attachment filename as initial post title Inform only the author when liking things General Options Fonts Colors Dianara stores data in this folder: &Save Configuration &Cancel Image files All files Invalid image The selected image is not valid. ContactCard Hometown Joined: %1 Updated: %1 Bio for %1 Abbreviation for Biography, but you can use the full word; %1=contact name This user doesn't have a biography No biography for %1 %1=contact name Open Profile in Web Browser Send Message Browse Messages In Lists... User Options Follow Stop Following Stop following? Are you sure you want to stop following %1? &Yes, stop following &No ContactList Type a partial name or ID to find a contact... F&ull List ContactManager username@server.org or https://server.org/username &Enter address to follow: &Follow Reload Followers Reload Following Export Followers Export Following Reload Lists &Neighbors Follo&wers Followin&g &Lists Export list of 'following' to a file Export list of 'followers' to a file DownloadWidget Download Save the attached file to your folders Cancel Save File As... All files Abort download? Do you want to stop downloading the attached file? &Yes, stop &No, continue Download aborted Download completed Download failed Downloading %1 KiB... %1 KiB downloaded EmailChanger Change E-mail Address Change &Cancel E-mail Address: Again: Your Password: E-mail addresses don't match! Password is empty! FilterEditor Filter Editor %1 if %2 contains: %3 This explains a filter rule, like: Hide if Author ID contains JohnDoe Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. Hide Highlight Post Contents Author ID Application Activity Description Keywords... &Add Filter Filters in use &Remove Selected Filter &Save Filters &Cancel if contains &New Filter C&urrent Filters FirstRunWizard Welcome Wizard Welcome to Dianara! This wizard will help you get started. You can access this window again at any time from the Help menu. The first step is setting up your account, by using the following button: Configure your &account Once you have configured your account, it's recommended that you edit your profile and add an avatar and some other information, if you haven't done so already. &Edit your profile By default, Dianara will post only to your followers, but it's recommended that you post to Public, at least sometimes. Post to &Public by default Open general program &help window &Show this again next time Dianara starts &Close FontPicker Change Choose a font HelpWidget Basic Help Getting started The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. At this point, your profile, contact lists and timelines will be loaded. You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. Settings You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. Timelines Contents Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. If you're new to Pump.io, take a look at this guide: Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. The main timeline, where you'll see all the stuff posted or shared by the people you follow. Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. Activity timeline, where you'll see your own posts, or posts shared by you. Favorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. Posting New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. It is possible to attach images, audio, video, and general files, like PDF documents, to your post. You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. If you add a specific person to the 'To' list, they will receive your message in their direct messages tab. Choose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. Managing contacts You can see the lists of people you follow, and who follow you from the Contacts tab. There, you can also manage person lists, used mainly to send posts to specific groups of people. You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. Keyboard controls Pump.io User Guide There are seven timelines: The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. LEFT SIDE should change to RIGHT SIDE on RTL languages The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). You can select who will see your post by using the To and Cc buttons. You can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. There is a text field at the top, where you can directly enter addresses of new contacts to follow them. You can also send a direct message (initially private) to that contact from this menu. You can find a list with some Pump.io users and other information here: Users by language The most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. Besides that, you can use: Control+Up/Down/PgUp/PgDown/Home/End to move around the timeline. Control+Left/Right to jump one page in the timeline. Control+G to go to any page in the timeline directly. Control+1/2/3 to switch between the minor feeds. Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. Dianara offers a D-Bus interface that allows some control from other applications. The interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. Command line options Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. Use the --debug parameter to have extra information in your terminal window, about what the program is doing. If your server does not support HTTPS, you can use the --nohttps parameter. If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. &Close ImageViewer Image &Save As... &Restart Animation &Close Save Image... Close Viewer Save Image As... Image files All files Error saving image There was a problem while saving %1. Filename should end in .jpg or .png extensions. ListsManager Name Members Add Mem&ber &Remove Member &Delete Selected List Add New &List Create L&ist &Add to List Are you sure you want to delete %1? 1=Name of a person list Remove person from list? Are you sure you want to remove %1 from the %2 list? 1=Name of a person, 2=name of a list &Yes Type a name for the new list... Type an optional description here WARNING: Delete list? &Yes, delete it &No LogViewer Log Clear &Log &Close MainWindow Side &Panel Status &Bar &Timeline The main timeline &Messages Messages sent explicitly to you &Activity Your own posts Your favorited posts &Contacts Initializing... Your account is not configured yet. Dianara started. Minor activities done by everyone, such as replying to posts Minor activities addressed to you Minor activities done by you The people you follow, the ones who follow you, and your person lists Press F1 for help Running with Qt v%1. Click here to configure your account &Session Auto-update &Timelines Mark All as Read &Post a Note &Quit &View &Toolbar Full &Screen &Log S&ettings Edit &Profile &Account Basic &Help Report a &Bug Pump.io User &Guide Some Pump.io &Tips List of Some Pump.io &Users Pump.io &Network Status Website Toolbar Open the log viewer Auto-updating enabled Auto-updating disabled Proxy password required You have configured a proxy server with authentication, but the password is not set. Enter the password for your proxy server: Starting automatic update of timelines, once every %1 minutes. Stopping automatic update of timelines. Received %1 older posts in '%2'. %1 is a number, %2 = name of a timeline 1 more pending to receive. singular, one post %1 more pending to receive. plural, several posts Also: Last update: %1 '%1' updated. %1 is the name of a feed Show Welcome Wizard 1 filtered out. singular, refers to a post %1 filtered out. plural, refers to posts 1 deleted. singular, refers to a post %1 deleted. plural, refers to posts There is 1 new activity. There are %1 new activities. 1 highlighted. singular, refers to an activity %1 highlighted. plural, refers to activities 1 filtered out. singular, refers to one activity %1 filtered out. plural, several activities No new activities. Error storing image! %1 bytes Marking everything as read... Closing due to environment shutting down... Quit? You are composing a note or a comment. Do you really want to close Dianara? &Yes, close the program &No Shutting down Dianara... System tray icon is not available. Dianara cannot be hidden in the system tray. Do you want to close the program completely? Timeline updated at %1. Update %1 Your Pump.io account is not configured Link to: %1 With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. English translation by JanKusanagi. TRANSLATORS: Change this with your language and name. If there was another translator before you, add your name after theirs ;) Dianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) &Configure Dianara &Filters and Highlighting &Help Visit &Website About &Dianara Your biography is empty Click to edit your profile 1 highlighted. singular, refers to a post %1 highlighted. plural, refers to posts No new posts. Total posts: %1 Favor&ites Received %1 older activities in '%2'. %1 is a number, %2 = name of feed Minor feed updated at %1. 1 more pending to receive. singular, 1 activity %1 more pending to receive. plural, several activities &Hide Window &Show Window There is 1 new post. There are %1 new posts. About Dianara Dianara is a pump.io social networking client. Thanks to all the testers, translators and packagers, who help make Dianara better! MinorFeed Older Activities Get previous minor activities There are no activities to show yet. Get %1 newer As in: Get 3 newer (activities) MinorFeedItem Using %1 Application used to generate this activity To: %1 1=people to whom this activity was sent Cc: %1 1=people to whom this activity was sent as CC Open referenced post MiscHelpers bytes PageSelector Jump to page Page number: &First As in: first page &Last As in: last page Newer As in: newer pages Older As in: older pages &Go &Cancel PeopleWidget &Search: Enter a name here to search for it Add a contact to a list &Cancel Post Via %1 Shared on %1 Loading image... Edited: %1 Posted on %1 1=Date In To Post Noun, not verb Using %1 1=Program used for posting or sharing If you select some text, it will be quoted. Share Unshare Unshare this post Edit Delete Open post in web browser Click to download the attachment Cc Copy post link to clipboard Normalize text colors &Close Type As in: type of object Modified on %1 Parent As in 'Open the parent post'. Try to use the shortest word! Open the parent post, to which this one replies Comment verb, for the comment button Reply to this post. Share this post with your contacts Modify this post Erase this post Join Group %1 members in the group Image is animated. Click on it to play. Couldn't load image! Attached Audio Attached Video Attached File %1 likes this One person %1 like this More than one person 1 like %1 likes 1 comment %1 comments %1 shared this %1 = One person name %1 shared this %1 = Names for more than one person Shared once Shared %1 times You like this Unlike Like this post Like Share post? Do you want to share %1's post? &Yes, share it &No Unshare post? Do you want to unshare %1's post? &Yes, unshare it WARNING: Delete post? Are you sure you want to delete this post? &Yes, delete it Click the image to see it in full size ProfileEditor Profile Editor This is your Pump address This is the e-mail address associated with your account, for things such as notifications and password recovery Change &E-mail... Change &Avatar... This is your visible name &Save Profile &Cancel Webfinger ID E-mail Avatar Full &Name &Hometown &Bio Not set In reference to the e-mail not being set for the account Select avatar image Image files All files Invalid image The selected image is not valid. ProxyDialog Proxy Configuration Do not use a proxy Your proxy username Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. &Save &Cancel Proxy &Type &Hostname &Port Use &Authentication &User Pass&word Publisher Select Picture... Find the picture in your folders Public Followers People... Select who will see this post To... Setting a title helps make the Meanwhile feed more informative Title Remove Cancel the attachment, and go back to a regular note Lists Select who will get a copy of this post Picture Audio Video Other as in other kinds of files Ad&d... Upload media, like pictures or videos Hit Control+Enter to post with the keyboard Cancel Cancel the post Picture not set Select Audio File... Find the audio file in your folders Audio file not set Select Video... Find the video in your folders Video not set Select File... Find the file in your folders File not set Error: Already composing You can't edit a post at this time, because a post is already being composed. Update Editing post You can't create a message for %1 at this time, because a post is already being composed. Posting failed. Try again. Warning: You have no followers yet You're trying to post to your followers only, but you don't have any followers yet. If you post like this, no one will be able to see your message. Do you want to make the post public instead of followers-only? &Yes, make it public &Cancel, go back to the post Updating... Post is empty. File not selected. Select one image Image files Select one file Invalid file The file type cannot be detected. All files Since you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. File is too big Dianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. This is a temporary measure, since the servers cannot set their own limits yet. Sorry for the inconvenience. Resolution Type Size %1 KiB of %2 KiB uploaded Invalid image Add a brief title for the post here (recommended) Cc... Post verb Note started from another application. Ignoring new note request from another application. &No, post to my followers only The image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. Select one audio file Audio files Invalid audio file The audio format cannot be detected. Select one video file Video files Invalid video file The video format cannot be detected. Posting... PumpController Creating person list... Deleting person list... Getting likes... Getting comments... Error connecting to %1 Unhandled HTTP error code %1 Message liked or unliked successfully. Comment %1 posted successfully. %1 is a piece of the comment Message deleted successfully. Likes received. Authorized to use account %1. Getting initial data. There is no authorized account. Getting list of 'Following'... Getting list of 'Followers'... Getting site users for %1... %1 is a server name Getting list of person lists... Getting a person list... Adding person to list... Removing person from list... Creating group... Joining group... Leaving group... Getting '%1'... %1 is the name of a feed Timeline Messages User timeline Uploading %1 1=filename HTTP error For the following HTTP error codesyou can check http://en.wikipedia.org/wiki/List_of_HTTP_status_codes in your language Gateway Timeout HTTP 504 error string Service Unavailable HTTP 503 error string Not Implemented HTTP 501 error string Internal Server Error HTTP 500 error string Gone HTTP 410 error string Not Found HTTP 404 error string Forbidden HTTP 403 error string Unauthorized HTTP 401 error string Bad Request HTTP 400 error string Moved Temporarily HTTP 302 error string Moved Permanently HTTP 301 error string Profile received. Followers Following Profile updated. E-mail updated: %1 %1 published successfully. Updating post content... %1 is the type of object: note, image... Untitled post %1 published successfully. %1 is a piece of the post Post %1 published successfully. %1 is the title of the post Avatar published successfully. Untitled post %1 updated successfully. %1 is a piece of the post Post %1 updated successfully. %1 is the title of the post Comment %1 updated successfully. %1 is a piece of the comment Adding items... Following %1 (%2) successfully. %1 is a person's name, %2 is the ID Stopped following %1 (%2) successfully. %1 is a person's name, %2 is the ID List of %1 users received. %1 is a server name Person list '%1' created successfully. %1 (%2) added to list successfully. 1=contact name, 2=contact ID %1 (%2) removed from list successfully. 1=contact name, 2=contact ID Group %1 created successfully. Group %1 joined successfully. Left the %1 group successfully. OAuth error while authorizing application. %1 attempts 1 attempt Some initial data was not received. Restarting initialization... List of 'following' completely received. The comments for this post cannot be loaded due to missing data on the server. Activity Favorites Meanwhile Mentions Actions 1 comment received. %1 comments received. Post by %1 shared successfully. 1=author of the post we are sharing Received '%1'. %1 is the name of a feed Partial list of 'following' received. List of 'followers' completely received. Partial list of 'followers' received. List of 'lists' received. Person list deleted successfully. Person list received. File downloaded successfully. File uploaded successfully. Posting message... Avatar uploaded. SSL errors in connection to %1! Loading external image from %1 regardless of SSL errors, as configured... %1 is a hostname The application is not registered with your server yet. Registering... Getting OAuth token... OAuth support error Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support. You probably need to install the OpenSSL plugin for QCA: %1, %2 or similar. Authorization error There was an OAuth error while trying to get the authorization token. QOAuth error %1 Application authorized successfully. Waiting for proxy password... Still waiting for profile. Trying again... Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally. All initial data received. Initialization complete. Ready. SiteUsersList You can get a list of the newest users registered on your server by clicking the button below. More resources to find users: Wiki page 'Users by language' PPump user search service at inventati.org Get list of users from your server Close list Loading... %1 users in %2 %1 = user count, %2 = server name TimeLine Welcome to Dianara Dianara is a <b>Pump.io</b> client. If you don't have a Pump account yet, you can get one at the following address, for instance: Press <b>F1</b> if you want to open the Help window. First, configure your account from the <b>Settings - Account</b> menu. After the process is done, your profile and timelines should update automatically. Take a moment to look around the menus and the Configuration window. You can also set your profile data and picture from the <b>Settings - Edit Profile</b> menu. There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information. Dianara's blog Pump.io User Guide Direct Messages Timeline Here, you'll see posts specifically directed to you. Activity Timeline You'll see your own posts here. Favorites Timeline Posts and comments you've liked. Newest Newer Older Requesting... Loading... Page %1 of %2. Showing %1 posts per page. %1 posts in total. Click here or press Control+G to jump to a specific page '%1' cannot be updated because a comment is currently being composed. %1 = feed's name %1 more posts pending for next update. Click here to receive them now. There are no posts Timestamp Invalid timestamp! A minute ago %1 minutes ago An hour ago %1 hours ago Just now In the future Yesterday %1 days ago A month ago %1 months ago A year ago %1 years ago UserPosts Posts by %1 Loading... &Close Received '%1'. %1 posts Error loading the timeline dianara-v1.3.2/translations/dianara_pl.ts000644 000764 000764 00000561626 12614520350 020075 0ustar00janjan000000 000000 ASActivity Public %1 by %2 1=kind of object: note, comment, etc; 2=author's name ASObject Note Noun, an object type Article Noun, an object type Image Noun, an object type Audio Noun, an object type Video Noun, an object type File Noun, an object type Comment Noun, as in object type: a comment Group Noun, an object type Collection Noun, an object type Other As in: other type of post No detailed location Deleted on %1 and one other and %1 others ASPerson Hometown Miasto AccountDialog Account Configuration Konfiguracja konta First, enter your Webfinger ID, your pump.io address. Najpierw podaj swój Webfinger ID, czyli adres pump.io. Your address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. Twój adres wygląda mniej więcej tak: użytkownik@serwerpump.org. Możesz go znaleźć na stronie serwera, w swoim profilu. If your profile is at https://pump.example/yourname, then your address is yourname@pump.example Jeżeli twój profil znajduje się pod adresem https://pump.przykład/twojeimię, twój adres to twojeimię@pump.przykład If you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. 1=link to website If you need help: %1 Pump.io User Guide Your Pump.io address: Twój adres pump.io: Webfinger ID, like username@pumpserver.org Webfinger ID, czyli użytkownik@serwerpump.org Your address, as username@server Twój adres w formacie użytkownik@serwer Get &Verifier Code Pobierz kod &weryfikacyjny After clicking this button, a web browser will open, requesting authorization for Dianara Po kliknięciu tego przycisku otworzy się przeglądarka i poprosi o dostęp dla Dianary Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. Don't translate the VERIFIER word! Gdy autoryzujesz Dianarę ze strony serwera Pump, otrzymasz kod weryfikacyjny VERIFIER. Skopiuj go i wklej w poniższe pole. Verifier code: Kod weryfikacyjny: Enter the verifier code provided by your Pump server here Tu wpisz kod weryfikacyjny otrzymany z serwera Pump Paste the verifier here Wklej tu kod weryfikacyjny &Authorize Application &Autoryzuj program &Save Details Zapi&sz informacje If the browser doesn't open automatically, copy this address manually Jeżeli przeglądarka nie otworzy się automatycznie, wejdź na ten adres &Cancel &Anuluj Your address, like username@pumpserver.org Enter or paste the verifier code provided by your Pump server here Your account is properly configured. Press Unlock if you wish to configure a different account. &Unlock A web browser will start now, where you can get the verifier code Otworzy się teraz przeglądarka, z której uzyskasz kod weryfikacyjny Your Pump address is invalid Twój adres Pump jest nieprawidłowy Verifier code is empty Nie podałeś kodu weryfikacyjnego Dianara is authorized to access your data Dianara uzyskała autoryzację do dostępu do twoich danych AudienceSelector 'To' List 'Cc' List &Add to Selected All Contacts Select people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below. ON THE LEFT should change to ON THE RIGHT in RTL languages Clear &List &Done &Cancel &Anuluj Selected People AvatarButton Open %1's profile in web browser Otwórz profil użytkownika %1 w przeglądarce Open your profile in web browser Otwórz swój profil w przeglądarce Send message to %1 Browse messages Stop following Przestań obserwować Follow Obserwuj Stop following? Na pewno przestać obserwować? Are you sure you want to stop following %1? Czy na pewno przestać obserwować użytkownika %1? &Yes, stop following &Tak, przestań obserwować &No &Nie ColorPicker Change Zmień Choose a color Comment Like or unlike this comment Quote This is a verb, infinitive Infinitive verb doesn't really work here Cytuj Reply quoting this comment Delete Usuń Posted on %1 Modified on %1 Edit Modify this comment Erase this comment Unlike Like %1 like this comment Plural: %1=list of people like John, Jane, Smith %1 polubili ten komentarz %1 likes this comment Singular: %1=name of just 1 person %1 polubił ten komentarz WARNING: Delete comment? UWAGA: Usunąć komentarz? Are you sure you want to delete this comment? Czy na pewno usunąć ten komentarz? &Yes, delete it &Tak, usuń go &No &Nie CommenterBlock You can press Control+Enter to send the comment with the keyboard Możesz wdusić Control+Enter aby wysłać komentarz przy użyciu klawiatury Comment Infinitive verb Reload comments &Cancel &Anuluj Cancel Press ESC to cancel the comment if there is no text Wciśnij Escape aby anulować komentarz jeżeli nie ma tekstu Check for comments Show all %1 comments Comments are not available Error: Already composing You can't edit a comment at this time, because another comment is already being composed. Editing comment Posting comment failed. Try again. Błąd przy wysyłaniu komentarza. Spróbuj ponownie. Sending comment... Wysyłanie komentarza... Updating comment... Comment is empty. Komentarz jest pusty. Composer Click here or press Control+N to post a note... Kliknij tutaj albo wciśnij Control+N aby zapostować... Symbols Symbole Formatting Formatowanie Normal Zwykły Bold Pogrubienie Italic Kursywa Underline Podkreślenie Strikethrough Przekreślenie Header Nagłówek List Table Preformatted block Clumsy, need tweaking Wcześniej sformatowany blok tekstu Quote block Cytat Make a link Dodaj odnośnik Insert an image from a web site Wstaw obrazek z adresu internetowego Insert line Wstaw linię &Format Button for text formatting and related options &Formatowanie Type a comment here Wpisz tutaj Insert as image? The link you are pasting seems to point to an image. Insert as visible image Insert as link Table Size How many rows (height)? How many columns (width)? Error: Invalid URL Błąd: Niepoprawny URL The address you entered (%1) is not valid. Image addresses should begin with http:// or https:// Wpisany adres (%1) jest niepoprawny. Adresy do obrazków muszą zaczynać się od http:// lub https:// Text Formatting Options Opcje formatowania tekstu Paste Text Without Formatting Wklej tekst bez formatowania Type a message here to post it Insert a link Type or paste a web address here. You could also select some text first, to turn it into a link. Make a link from selected text Type or paste a web address here. The selected text (%1) will be converted to a link. Insert an image from a URL Type or paste the image address here. The link must point to the image file directly. Cancel message? Are you sure you want to cancel this message? &Yes, cancel it &No &Nie ConfigDialog Program Configuration Konfiguracja programu minutes minut Timeline &update interval Częstotliwość akt& &ualizacji osi czasu posts Goes after a number, as: 25 posts postów &Posts per page, main timeline Wyświetlanie &postów na stronie (w głównej osi czasu) posts This goes after a number, like: 10 posts postów Posts per page, &other timelines Top Bottom &Tabs position &Movable tabs characters This is a suffix, after a number Snippet limit Post Titles Post Contents Minor Feeds Only for images inserted from web sites. Use with care. Use attachment filename as initial post title Inform only the author when liking things System Tray Icon &Type Timelines Posts Notifications System Tray Dianara stores data in this folder: &Save Configuration Public posts as &default Pro&xy Settings Network configuration Set Up F&ilters Filtering rules Highlighted activities, except mine Any highlighted activity Always Never Comments Left side tabs on left side/west; RTL not affected Right side tabs on right side/east; RTL not affected You are among the recipients of the activity, such as a comment addressed to you. Used also when highlighting posts addressed to you in the timelines. The activity is in reply to something done by you, such as a comment posted in reply to one of your notes. You are the object of the activity, such as someone adding you to a list. The activity is related to one of your objects, such as someone liking one of your posts. Used also when highlighting your own posts in the timelines. Item highlighted due to filtering rules. Item is new. Show snippets in minor feeds Show information for deleted posts Hide duplicated posts Jump to new posts line on update Avatar size Show extended share information Show extra information Highlight post author's comments Highlight your own comments Ignore SSL errors in images Show character counter Don't inform followers when following someone Don't inform followers when handling lists As system notifications Using own notifications Don't show notifications Notification Style Notify when receiving: New posts Highlighted posts New activities in minor feed Highlighted activities in minor feed Default System iconset, if available Show your current avatar Custom icon S&elect... Custom &Icon Hide window on startup General Options Fonts Colors Composer Privacy This is a system notification System notifications are not available! Own notifications will be used. This is a basic notification Select custom icon Image files Pliki graficzne All files Wszystkie pliki Invalid image Niepoprawny obrazek The selected image is not valid. Wybrany obrazek nie jest poprawny. &Cancel &Anuluj ContactCard Hometown Miasto Joined: %1 Updated: %1 Bio for %1 Abbreviation for Biography, but you can use the full word; %1=contact name Opis %1 This user doesn't have a biography No biography for %1 %1=contact name Brak opisu dla %1 Open Profile in Web Browser Send Message Browse Messages In Lists... User Options Follow Obserwuj Stop Following Stop following? Na pewno przestać obserwować? Are you sure you want to stop following %1? Czy na pewno przestać obserwować użytkownika %1? &Yes, stop following &Tak, przestań obserwować &No &Nie ContactList Type a partial name or ID to find a contact... F&ull List ContactManager username@server.org or https://server.org/username &Enter address to follow: &Follow Reload Followers Reload Following Export Followers Export Following Reload Lists &Neighbors Follo&wers Followin&g &Lists Export list of 'following' to a file Export list of 'followers' to a file DownloadWidget Download Save the attached file to your folders Cancel Save File As... All files Wszystkie pliki Abort download? Do you want to stop downloading the attached file? &Yes, stop &No, continue Download aborted Download completed Download failed Downloading %1 KiB... %1 KiB downloaded EmailChanger Change E-mail Address Change Zmień &Cancel &Anuluj E-mail Address: Again: Your Password: E-mail addresses don't match! Password is empty! FilterEditor Filter Editor %1 if %2 contains: %3 This explains a filter rule, like: Hide if Author ID contains JohnDoe Here you can set some rules for hiding or highlighting stuff. You can filter by content, author or application. For instance, you can filter out messages posted by the application Open Farm Game, or which contain the word NSFW in the message. You could also highlight messages that contain your name. Hide Highlight Post Contents Author ID Application Activity Description Keywords... &Add Filter Filters in use &Remove Selected Filter &Save Filters &Cancel &Anuluj if contains &New Filter C&urrent Filters FirstRunWizard Welcome Wizard Welcome to Dianara! This wizard will help you get started. You can access this window again at any time from the Help menu. The first step is setting up your account, by using the following button: Configure your &account Once you have configured your account, it's recommended that you edit your profile and add an avatar and some other information, if you haven't done so already. &Edit your profile By default, Dianara will post only to your followers, but it's recommended that you post to Public, at least sometimes. Post to &Public by default Open general program &help window &Show this again next time Dianara starts &Close FontPicker Change Zmień Choose a font HelpWidget Basic Help Getting started Settings Timelines Posting Managing contacts Keyboard controls Command line options Contents The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. At this point, your profile, contact lists and timelines will be loaded. You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. If you're new to Pump.io, take a look at this guide: Pump.io User Guide You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. There are seven timelines: The main timeline, where you'll see all the stuff posted or shared by the people you follow. Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. Activity timeline, where you'll see your own posts, or posts shared by you. Favorites timeline, where you'll see the posts and comments you've liked. This can be used as a bookmark system. The fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. LEFT SIDE should change to RIGHT SIDE on RTL languages The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. It is possible to attach images, audio, video, and general files, like PDF documents, to your post. You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. You can select who will see your post by using the To and Cc buttons. If you add a specific person to the 'To' list, they will receive your message in their direct messages tab. You can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. Choose one with the arrow keys and press Enter to complete the name. This will add that person to the recipients list. You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. You can see the lists of people you follow, and who follow you from the Contacts tab. There, you can also manage person lists, used mainly to send posts to specific groups of people. There is a text field at the top, where you can directly enter addresses of new contacts to follow them. Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. You can also send a direct message (initially private) to that contact from this menu. You can find a list with some Pump.io users and other information here: Users by language The most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. Besides that, you can use: Control+Up/Down/PgUp/PgDown/Home/End to move around the timeline. Control+Left/Right to jump one page in the timeline. Control+G to go to any page in the timeline directly. Control+1/2/3 to switch between the minor feeds. Control+Enter to post, when you're done composing a note or a comment. If the note is empty, you can cancel it by pressing ESC. While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. Control+Enter to finish creating a list of recipients for a post, in the 'To' or 'Cc' lists. You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. Use the --debug parameter to have extra information in your terminal window, about what the program is doing. If your server does not support HTTPS, you can use the --nohttps parameter. Dianara offers a D-Bus interface that allows some control from other applications. The interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. &Close ImageViewer Image &Save As... &Restart Animation &Close Save Image... Close Viewer Save Image As... Image files Pliki graficzne All files Wszystkie pliki Error saving image There was a problem while saving %1. Filename should end in .jpg or .png extensions. ListsManager Name Members Add Mem&ber &Remove Member &Delete Selected List Add New &List Type a name for the new list... Type an optional description here Create L&ist &Add to List WARNING: Delete list? Are you sure you want to delete %1? 1=Name of a person list &Yes, delete it &Tak, usuń go &No &Nie Remove person from list? Are you sure you want to remove %1 from the %2 list? 1=Name of a person, 2=name of a list &Yes LogViewer Log Clear &Log &Close MainWindow Meanwhile... W międzyczasie... The main timeline Główna oś czasu Messages sent explicitly to you Wiadomości wysłane bezpośrednio do ciebie Your own posts Twoje własne wiadomości Your favorited posts Twoje ulubione wiadomości &Contacts &Kontakty Initializing... Inicjalizacja... Your account is not configured yet. Twoje konto nie zostało jeszcze skonfigurowane. Dianara started. Dianara wystartowała. The people you follow, the ones who follow you, and your person lists Running with Qt v%1. &Session &Sesja Update Minor &Feed Zaktualizuj &mniejszą oś czasu Auto-update &Timelines Mark All as Read Oznacz wszystkie jako przeczytane &Post a Note Wyślij &wiadomość &Quit &Zakończ &View &Widok Side &Panel Panel &boczny &Toolbar Status &Bar Pasek &stanu Full &Screen Pełny &ekran &Log &Dziennik wiadomości S&ettings &Ustawienia Edit &Profile Edytuj &profil &Configure Dianara &Konfiguracja Dianary &Help &Pomoc Basic &Help Visit &Website Odwiedź &stronę www Some Pump.io &Tips List of Some Pump.io &Users Pump.io &Network Status Website About &Dianara &O Dianarze Toolbar Open the log viewer Auto-updating enabled Auto-updating disabled Proxy password required You have configured a proxy server with authentication, but the password is not set. Enter the password for your proxy server: Your biography is empty Click to edit your profile Starting automatic update of timelines, once every %1 minutes. Stopping automatic update of timelines. Received %1 older posts in '%2'. %1 is a number, %2 = name of a timeline 1 more pending to receive. singular, one post %1 more pending to receive. plural, several posts Also: Last update: %1 '%1' updated. %1 is the name of a feed Click here to configure your account Show Welcome Wizard 1 highlighted. singular, refers to a post %1 highlighted. plural, refers to posts 1 filtered out. singular, refers to a post %1 filtered out. plural, refers to posts 1 deleted. singular, refers to a post %1 deleted. plural, refers to posts No new posts. Favor&ites Your Pump.io account is not configured Received %1 older activities in '%2'. %1 is a number, %2 = name of feed 1 more pending to receive. singular, 1 activity %1 more pending to receive. plural, several activities 1 filtered out. singular, refers to one activity %1 filtered out. plural, several activities Error storing image! %1 bytes Marking everything as read... Closing due to environment shutting down... Shutting down Dianara... System tray icon is not available. Dianara cannot be hidden in the system tray. Do you want to close the program completely? Minor feed updated at %1. Update %1 There is 1 new activity. There are %1 new activities. 1 highlighted. singular, refers to an activity %1 highlighted. plural, refers to activities No new activities. Link to: %1 With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. Thanks to all the testers, translators and packagers, who help make Dianara better! English translation by JanKusanagi. TRANSLATORS: Change this with your language and name. If there was another translator before you, add your name after theirs ;) Autor polskiego tłumaczenia: Łukasz "Cyber Killer" Korpalski i Derping Muffins. Dianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) &Hide Window &Show Window &Pokaż okno Quit? You are composing a note or a comment. Do you really want to close Dianara? &Yes, close the program &No &Nie &Account There is 1 new post. Jest 1 nowa wiadomość. There are %1 new posts. Są %1 nowe wiadomości. Timeline updated. Oś czasu zaktualizowana. Minor activities done by everyone, such as replying to posts Minor activities addressed to you Minor activities done by you Press F1 for help &Filters and Highlighting Report a &Bug Pump.io User &Guide Timeline updated at %1. Oś czasu zaktualizowana o %1. Total posts: %1 &Timeline &Oś czasu &Messages &Wiadomości &Activity &Aktywność About Dianara O Dianarze Dianara is a pump.io social networking client. Dianara jest klientem sieci społecznościowej pump.io. MinorFeed Older Activities Get previous minor activities There are no activities to show yet. Get %1 newer As in: Get 3 newer (activities) MinorFeedItem Using %1 Application used to generate this activity To: %1 1=people to whom this activity was sent Cc: %1 1=people to whom this activity was sent as CC Open referenced post MiscHelpers bytes PageSelector Jump to page Page number: &First As in: first page &Last As in: last page Newer As in: newer pages Older As in: older pages &Go &Cancel &Anuluj PeopleWidget &Search: Enter a name here to search for it Add a contact to a list &Cancel &Anuluj Post Via %1 Type As in: type of object Posted on %1 1=Date Modified on %1 To Using %1 1=Program used for posting or sharing Share Delete Usuń Loading image... Attached File %1 likes this One person %1 like this More than one person 1 like %1 likes 1 comment %1 comments %1 shared this %1 = One person name %1 shared this %1 = Names for more than one person Click to download the attachment Post Noun, not verb Shared on %1 Open post in web browser Copy post link to clipboard Normalize text colors In If you select some text, it will be quoted. Unshare Unshare this post Edit Comment verb, for the comment button Cc &Close Parent As in 'Open the parent post'. Try to use the shortest word! Open the parent post, to which this one replies Reply to this post. Share this post with your contacts Modify this post Erase this post Join Group %1 members in the group Image is animated. Click on it to play. Couldn't load image! Attached Audio Attached Video Shared once Shared %1 times Edited: %1 You like this Unlike Like this post Like Share post? Do you want to share %1's post? &Yes, share it &No &Nie Unshare post? Do you want to unshare %1's post? &Yes, unshare it WARNING: Delete post? Are you sure you want to delete this post? &Yes, delete it &Tak, usuń go Click the image to see it in full size ProfileEditor Profile Editor Edytor profilu This is your Pump address To jest twój adres Pump This is the e-mail address associated with your account, for things such as notifications and password recovery To jest adres e-mail powiązany z twoim kontem, używany do powiadomień i odzyskiwania hasła Change &E-mail... Change &Avatar... Zmień &awatar This is your visible name To jest twoje wyświetlane imię &Save Profile &Zapisz profil &Cancel &Anuluj Webfinger ID E-mail Avatar Awatar Full &Name Pełne &imię &Hometown Miasto &Bio &Coś o tobie Not set In reference to the e-mail not being set for the account Nie ustawiony Select avatar image Wybierz obrazek Image files Pliki graficzne All files Wszystkie pliki Invalid image Niepoprawny obrazek The selected image is not valid. Wybrany obrazek nie jest poprawny. ProxyDialog Proxy Configuration Do not use a proxy Your proxy username Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. &Save &Cancel &Anuluj Proxy &Type &Hostname &Port Use &Authentication &User Pass&word Publisher Select Picture... Find the picture in your folders Public Title Followers To... Select who will see this post Select who will get a copy of this post Lists Setting a title helps make the Meanwhile feed more informative Remove Cancel the attachment, and go back to a regular note People... Cc... Picture Audio Video Other as in other kinds of files Ad&d... Upload media, like pictures or videos Hit Control+Enter to post with the keyboard Cancel Cancel the post Picture not set Select Audio File... Find the audio file in your folders Audio file not set Select Video... Find the video in your folders Video not set Select File... Find the file in your folders File not set Error: Already composing You can't edit a post at this time, because a post is already being composed. Update Editing post You can't create a message for %1 at this time, because a post is already being composed. Posting failed. Try again. Warning: You have no followers yet You're trying to post to your followers only, but you don't have any followers yet. If you post like this, no one will be able to see your message. Do you want to make the post public instead of followers-only? &Yes, make it public &Cancel, go back to the post Posting... Updating... Post is empty. File not selected. Select one image Image files Pliki graficzne Select one file Invalid file The file type cannot be detected. All files Wszystkie pliki Since you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. File is too big Dianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. This is a temporary measure, since the servers cannot set their own limits yet. Sorry for the inconvenience. Resolution Type Size %1 KiB of %2 KiB uploaded Invalid image Niepoprawny obrazek Add a brief title for the post here (recommended) Post verb Note started from another application. Ignoring new note request from another application. &No, post to my followers only The image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. Select one audio file Audio files Invalid audio file The audio format cannot be detected. Select one video file Video files Invalid video file The video format cannot be detected. PumpController Getting list of 'Following'... Getting list of 'Followers'... Getting site users for %1... %1 is a server name Getting list of person lists... Creating person list... Deleting person list... Getting a person list... Adding person to list... Removing person from list... Creating group... Joining group... Leaving group... Getting likes... Getting comments... Getting '%1'... %1 is the name of a feed Timeline Messages User timeline Uploading %1 1=filename HTTP error For the following HTTP error codesyou can check http://en.wikipedia.org/wiki/List_of_HTTP_status_codes in your language Gateway Timeout HTTP 504 error string Service Unavailable HTTP 503 error string Not Implemented HTTP 501 error string Internal Server Error HTTP 500 error string Gone HTTP 410 error string Not Found HTTP 404 error string Forbidden HTTP 403 error string Unauthorized HTTP 401 error string Bad Request HTTP 400 error string Moved Temporarily HTTP 302 error string Moved Permanently HTTP 301 error string Error connecting to %1 Unhandled HTTP error code %1 Profile received. Followers Following Profile updated. E-mail updated: %1 Avatar published successfully. Message liked or unliked successfully. 1 comment received. %1 comments received. Post by %1 shared successfully. 1=author of the post we are sharing Received '%1'. %1 is the name of a feed Adding items... Message deleted successfully. List of 'following' completely received. Partial list of 'following' received. List of 'followers' completely received. Partial list of 'followers' received. List of %1 users received. %1 is a server name Person list deleted successfully. Person list received. File downloaded successfully. File uploaded successfully. Posting message... Avatar uploaded. OAuth error while authorizing application. %1 attempts 1 attempt Some initial data was not received. Restarting initialization... Likes received. Authorized to use account %1. Getting initial data. There is no authorized account. The comments for this post cannot be loaded due to missing data on the server. Activity Favorites Meanwhile Mentions Actions %1 published successfully. Updating post content... %1 is the type of object: note, image... Untitled post %1 published successfully. %1 is a piece of the post Post %1 published successfully. %1 is the title of the post Untitled post %1 updated successfully. %1 is a piece of the post Post %1 updated successfully. %1 is the title of the post Comment %1 updated successfully. %1 is a piece of the comment Comment %1 posted successfully. %1 is a piece of the comment Following %1 (%2) successfully. %1 is a person's name, %2 is the ID Stopped following %1 (%2) successfully. %1 is a person's name, %2 is the ID List of 'lists' received. Person list '%1' created successfully. %1 (%2) added to list successfully. 1=contact name, 2=contact ID %1 (%2) removed from list successfully. 1=contact name, 2=contact ID Group %1 created successfully. Group %1 joined successfully. Left the %1 group successfully. SSL errors in connection to %1! Loading external image from %1 regardless of SSL errors, as configured... %1 is a hostname The application is not registered with your server yet. Registering... Getting OAuth token... OAuth support error Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support. You probably need to install the OpenSSL plugin for QCA: %1, %2 or similar. Authorization error There was an OAuth error while trying to get the authorization token. QOAuth error %1 Application authorized successfully. Waiting for proxy password... Still waiting for profile. Trying again... Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally. All initial data received. Initialization complete. Ready. SiteUsersList You can get a list of the newest users registered on your server by clicking the button below. More resources to find users: Wiki page 'Users by language' PPump user search service at inventati.org Get list of users from your server Close list Loading... %1 users in %2 %1 = user count, %2 = server name TimeLine Welcome to Dianara First, configure your account from the <b>Settings - Account</b> menu. After the process is done, your profile and timelines should update automatically. Take a moment to look around the menus and the Configuration window. You can also set your profile data and picture from the <b>Settings - Edit Profile</b> menu. Dianara's blog If you don't have a Pump account yet, you can get one at the following address, for instance: Dianara is a <b>Pump.io</b> client. Press <b>F1</b> if you want to open the Help window. There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information. Pump.io User Guide Direct Messages Timeline Here, you'll see posts specifically directed to you. Activity Timeline You'll see your own posts here. Favorites Timeline Posts and comments you've liked. Newest Newer Older Requesting... Loading... Page %1 of %2. Showing %1 posts per page. %1 posts in total. Click here or press Control+G to jump to a specific page '%1' cannot be updated because a comment is currently being composed. %1 = feed's name %1 more posts pending for next update. Click here to receive them now. There are no posts Timestamp Invalid timestamp! Zły czas! A minute ago Minutę temu %1 minutes ago %1 minut temu An hour ago Godzinę temu %1 hours ago %1 godzin temu Just now In the future Yesterday %1 days ago %1 dni temu A month ago Miesiąc temu %1 months ago %1 miesięcy temu A year ago Rok temu %1 years ago %1 lat temu UserPosts Posts by %1 Loading... &Close Received '%1'. %1 posts Error loading the timeline dianara-v1.3.2/translations/dianara_pl.qm000664 000764 000764 00000040572 12557425000 020060 0ustar00janjan000000 000000 DQ~UVF+_}/¹eik0!l0,602H5Vgv/YvR[e26e` %UkЅC3"R({"*E*0+v<+;+<+=+x,}H2>cL_<<+@)D,cBjH4H:*YHHD IaF.K+MeE7NtNOq-P7P7wRS PTMTV*UVVQWjQWL_mXXƥ#.Y0+Zy%5e[ %`[!`L\\aj Dknq׎VwjfyT8ybzz>}"6>hN&cQ>cG|Di}M^."4bJ̸@ՅH^I5 %ҥH~T7[*Pr-UNvf>i~15_;dJݣ4sK$YD&-!00a<#<{DInS"M$#P3QE2 RS2V8 \q:loVp`N=#,0or\h>H^Jgt=8jL D|=Σ% 2X5 $Uc UY*%%YL3u>KL=wQ+RxN=hBzpH5tɥPv3VwXSrt[J-a^ Njt 66j6ߥ66#,6E{ n nMT!Uƭ>}Z4uVϠN(ӻ'؞Bdls 53IJ8Y3p#wJm  V I p}xmaM vSKZ$1"$1/(Jk.s0cQ0c"6L6s\6's70$eFEA9OM.YXR@/ZN Z?di6q,ijCl#sC!wh3x2zpV{eZ}z..Q&h]Nno^m5Blb~¿`¿—ÌP^yjn`q"?q-޷%?r_ o#n !Mn%?X1ʳf 11 NL#nX$K6U> B)P Tj`3!a$gUTr) M|}x::g?V76< "6U>7]@"I$uItID,x I6Aif, ,(<3n<ԁi^BcymH4Mͣ03g3giրvR|@{|z/(74*WlF 2/<#.,v4w>1KTKTkLܤRMpCRpV|3]L.]Kabcfy7h9~fghT/2jzds}u_yz:{7{T |7f}L).,.{FT;VVg68h>\=4L>T'tGGvP:4AúXo\woj0#02]ƨ`23on:tsu-NTۡأQe"*j+!-^U4uJ4u74uPH9$Tk<.55<.\<.iJ='At ACA4GmbAqI7M?$sRQ(BT$VхjZP:ZzZf;3fgehC(mn{np{0#r7QuOy9~a/ N/nu`nw3]NkC i#YQm i@rk%ng E"i"k#N)2K4n2?-6~~F*Mrcr\]eqie7qkZ$s#Tsn`|2fX|,n|SU3DtT'hvehB>zBU/vq;iA`*Jrnť,m]+8琊--QyRjn ! -Q 0\~ 0u~A 1`#=b 6 I 7w2t Sg^p ^n r%# s$ w>sNt | ~5 o tAt 4 n C~. ~ b+ z l `  o  b~< fZy ʞm Uy p^` & ~~\ Ԭ? [n3 t*F SJ hCB NeN A A3 ˊ ~| n$ o ;p !/& #x- (O: ?m @=c @Cch HkP Tu TGk3 TG [ds+ ]?Z cP d 4$k d< d<% d<' pO1j q\. v'd& z3. _> . d3 N I I@ I=$ Ixl I{ Is I I4 I<) )* ~ I j y* Ng o%I ` tBQ U= ^ '_( t< ՁD 3Gni ֓, N %V T" lV ؄E K 0)Z :&6 }  >} ~ ~ ~ S c53 [ z "e8 "0 ,i4& -֮ 02 5<g 7"C <خws =/n{ @1 A#o ED! XF7 Ya) bX& bXI< eTA f( f*25 mmT$ o> y[>O |6? P/O !q q3g SA #W  V$   N A )X  U " sd 5} jm OCy( أ1@ y7 j C?  2 Ғ` >X (=&\ - U5n /\ 67 =x BX EL Y2 [c ]ӡp `uX d.Ps eMZ^ j8z {] {8 F[ < M rwa V x  q7 # s ͡ =V (O Б*' O! < yt |SG9 > w4 ac =U  Е #pn\ )od . Bu G: NnT V, _Pa, b e&R h1j iFCq 1 @" |  ; >~ ^b q[ ˽+1  b8; %  Ʈ `% VS:3N2.mT+jB/7D6R//RT3^Xc[IgO?lNl?ltzlzl5lCmhnm{y$8T8T:8T#{>Q?'B|NoT'&Z^U~A%W!jc LE!.0U=O/UykR  #us`^_^iz9A'5%7'5%N'5%'E)d+< ,0"A BtnPB}~:iC%E.?FrJMWPdT>O_}0eY.ݹuW`i-^2^*/~nr.@Az^Ja~d ؞¹e8X$We%~}idzOi"SڨdRQB#nޝvi%1 de %2%1 by %2 ASActivity PblicPublic ASActivityArticleArticleASObject udioAudioASObjectCollecci CollectionASObjectComentariCommentASObjectEliminat el %1 Deleted on %1ASObject ArxiuFileASObjectGrupGroupASObject ImatgeImageASObject6No hi ha ubicaci detalladaNo detailed locationASObjectNotaNoteASObject AltreOtherASObject VdeoVideoASObjecti %1 ms and %1 othersASObjecti un ms and one otherASObject CiutatHometownASPerson.&Autoritzar l'aplicaci&Authorize Application AccountDialog&Cancellar&Cancel AccountDialog&Guardar dades &Save Details AccountDialog&Desbloquejar&Unlock AccountDialogAra s'iniciar un navegador web, on podrs obtenir el codi de verificaciAA web browser will start now, where you can get the verifier code AccountDialog.Configuraci del compteAccount Configuration AccountDialogQuan premis aquest bot, s'obrir un navegador web, demanant autoritzaci per DianaraYAfter clicking this button, a web browser will open, requesting authorization for Dianara AccountDialogbDianara te autoritzaci per accedir al teu compte)Dianara is authorized to access your data AccountDialogIntrodueix o enganxa aqu el codi de verificaci proporcionat pel teu servidor PumpBEnter or paste the verifier code provided by your Pump server here AccountDialogPrimer, introdueix el teu identificador Webfinger, la teva adrea pump.io.5First, enter your Webfinger ID, your pump.io address. AccountDialog8Obtenir codi de &verificaciGet &Verifier Code AccountDialogSi el navegador web no s'obre automticament, copia aquesta adrea manualmentEIf the browser doesn't open automatically, copy this address manually AccountDialogSi encara no tens un compte, pots registrar-ne un a %1. Aquest enlla et portar a un servidor pblic aleatori.sIf you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. AccountDialog.Si necessites ajuda: %1If you need help: %1 AccountDialogSi el teu perfil es troba a https://pump.exemple/elteunom, llavors la teva adrea es elteunom@pump.exemple_If your profile is at https://pump.example/yourname, then your address is yourname@pump.example AccountDialog8Un cop que hagis autoritzat a Dianara des de la interfcie web del teu servidor Pump, rebrs un codi anomenat VERIFIER. Copia'l i enganxa'l al camp de sota.Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. AccountDialogpPrem Desbloquejar si vols configurar un compte diferent.:Press Unlock if you wish to configure a different account. AccountDialog0Guia d'usuari de Pump.ioPump.io User Guide AccountDialog@El codi de verificaci est buitVerifier code is empty AccountDialog(Codi de verificaci:Verifier code: AccountDialog@La teva adrea Pump no es vlidaYour Pump address is invalid AccountDialog.La teva adrea pump.io:Your Pump.io address: AccountDialogVEl teu compte est configurat correctament.$Your account is properly configured. AccountDialogLa teva adrea es com usuari@servidorpump.org, i pots trobar-la al teu perfil, a la interfcie web.kYour address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. AccountDialogVLa teva adrea, com usuari@servidorpump.org*Your address, like username@pumpserver.org AccountDialog,&Afegir a seleccionats&Add to SelectedAudienceSelector&Cancellar&CancelAudienceSelector&Fet&DoneAudienceSelectorLlista 'Cc' 'Cc' ListAudienceSelectorLlista 'Per a' 'To' ListAudienceSelector$Tots els contactes All ContactsAudienceSelector Esborrar &llista Clear &ListAudienceSelector:Selecciona gent de la llista de l'esquerra. Pots arrossegar-los amb el ratol, fer clic o doble clic en ells, o seleccionar-los i fer servir el bot de sota.:ON THE LEFT should change to ON THE RIGHT in RTL languagesSelect people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below.AudienceSelector"Gent seleccionadaSelected PeopleAudienceSelector&No&No AvatarButton*&S, deixar de seguir&Yes, stop following AvatarButton\Ests segur de que vols deixar de seguir a %1?+Are you sure you want to stop following %1? AvatarButtonVeure missatgesBrowse messages AvatarButton SeguirFollow AvatarButtonLObrir el perfil de %1 al navegador web Open %1's profile in web browser AvatarButtonHObrir el teu perfil al navegador web Open your profile in web browser AvatarButton(Enviar missatge a %1Send message to %1 AvatarButton Deixar de seguirStop following AvatarButton"Deixar de seguir?Stop following? AvatarButtonCanviarChange ColorPickerEscull un colorChoose a color ColorPickerFA %1 els hi agrada aquest comentari%1 like this commentComment>A %1 li agrada aquest comentari%1 likes this commentComment&No&NoComment &S, eliminar-ho&Yes, delete itCommentdEsts segur de que vols eliminar aquest comentari?-Are you sure you want to delete this comment?CommentEliminarDeleteComment EditarEditComment2Esborrar aquest comentariErase this commentCommentM'agradaLikeCommentlDir que t'agrada o que ja no t'agrada aquest comentariLike or unlike this commentCommentModificat el %1Modified on %1Comment4Modificar aquest comentariModify this commentCommentPublicat el %1 Posted on %1Comment CitarQuoteCommentBRespondre citant aquest comentariReply quoting this commentCommentJa no m'agradaUnlikeComment2AVS: Eliminar comentari?WARNING: Delete comment?CommentCancellarCancelCommenterBlock(Comprovar comentarisCheck for commentsCommenterBlockComentarCommentCommenterBlock.El comentari est buit.Comment is empty.CommenterBlockNEls comentaris no es troben disponiblesComments are not availableCommenterBlock"Editant comentariEditing commentCommenterBlock4Error: Ja s'est redactantError: Already composingCommenterBlocktHa fallat la publicaci del comentari. Torna-ho a provar.#Posting comment failed. Try again.CommenterBlockjPrem ESC per cancellar el comentari si no hi ha text3Press ESC to cancel the comment if there is no textCommenterBlock,Actualitzar comentarisReload commentsCommenterBlock(Enviant comentari...Sending comment...CommenterBlock2Mostrar els %1 comentarisShow all %1 commentsCommenterBlock2Actualitzant comentari...Updating comment...CommenterBlock~Pots premer Control+Enter per enviar el comentari amb el teclatAYou can press Control+Enter to send the comment with the keyboardCommenterBlockNo pots editar un comentari en aquest moment, perque ja s'est redactant un altre comentari.YYou can't edit a comment at this time, because another comment is already being composed.CommenterBlock&Format&FormatComposer&No&NoComposer$&S, cancellar-ho&Yes, cancel itComposerTSegur que vols cancellar aquest missatge?-Are you sure you want to cancel this message?ComposerNegretaBoldComposer.Cancellar el missatge?Cancel message?ComposernFes clic aqu o prem Control+N per publicar una nota.../Click here or press Control+N to post a note...Composer(Error: URL no vlidaError: Invalid URLComposer Format FormattingComposer TtolHeaderComposer6Quantes columnes (amplada)?How many columns (width)?Composer.Quantes files (alada)?How many rows (height)?Composer"Inserir un enlla Insert a linkComposer@Inserir una imatge des d'una URLInsert an image from a URLComposerHInserir una imatge des d'un lloc webInsert an image from a web siteComposer&Inserir com imatge?Insert as image?Composer$Inserir com enllaInsert as linkComposer4Inserir com imatge visibleInsert as visible imageComposerInserir lnia Insert lineComposerCursivaItalicComposer LlistaListComposerFer un enlla Make a linkComposer@Fer un link del text seleccionatMake a link from selected textComposer NormalNormalComposer4Enganxar text sense formatPaste Text Without FormattingComposer Bloc preformatatPreformatted blockComposerBloc de cita Quote blockComposer Barrat StrikethroughComposerSmbolsSymbolsComposer TaulaTableComposer Mida de la taula Table SizeComposer2Opcions de format de textText Formatting OptionsComposerL'adrea que has introduit (%1) no es vlida. Les adreces d'imatges han de comenar amb http:// o https://`The address you entered (%1) is not valid. Image addresses should begin with http:// or https://ComposerxSembla que l'enlla que ests enganxant apunta a una imatge.4The link you are pasting seems to point to an image.Composer0Escriu un comentari aquType a comment hereComposerNEscriu un missatge aqu per publicar-hoType a message here to post itComposerEscriu o enganxa una adrea web aqu. El text seleccionat (%1) ser convertit en un enlla.UType or paste a web address here. The selected text (%1) will be converted to a link.ComposerEscriu o enganxa una adrea web aqu. Tamb pots seleccionar text abans, per convertir-ho en un enlla.`Type or paste a web address here. You could also select some text first, to turn it into a link.ComposerEscriu o enganxa l'adrea de la imatge aqu. L'enlla ha d'apuntar a l'arxiu d'imatge directament.UType or paste the image address here. The link must point to the image file directly.ComposerSubratllat UnderlineComposer&Cancellar&Cancel ConfigDialog"Pes&tanyes mbils &Movable tabs ConfigDialog^&Missatges per pgina, lnia temporal principal&Posts per page, main timeline ConfigDialog*&Guardar configuraci&Save Configuration ConfigDialog2Posici de les &pestanyes&Tabs position ConfigDialogTots els arxius All files ConfigDialog SempreAlways ConfigDialog:Qualsevol activitat destacadaAny highlighted activity ConfigDialog>Com a notificacions del sistemaAs system notifications ConfigDialog"Mida dels avatars Avatar size ConfigDialogPart inferiorBottom ConfigDialog ColorsColors ConfigDialogComentarisComments ConfigDialog EditorComposer ConfigDialog*&Icona personalitzada Custom &Icon ConfigDialog(Icona personalitzada Custom icon ConfigDialogPredeterminadaDefault ConfigDialogZDianara emmagatzema dades en aquesta carpeta:#Dianara stores data in this folder: ConfigDialogTNo informar als seguidors al seguir a alg-Don't inform followers when following someone ConfigDialog\No informar als seguidors al manipular llistes*Don't inform followers when handling lists ConfigDialog0No mostrar notificacionsDon't show notifications ConfigDialog"Normes de filtratFiltering rules ConfigDialogTipus de lletraFonts ConfigDialog Opcions generalsGeneral Options ConfigDialog6Ocultar missatges duplicatsHide duplicated posts ConfigDialog4Ocultar finestra a l'iniciHide window on startup ConfigDialogVDestacar comentaris de l'autor del missatge Highlight post author's comments ConfigDialogFDestacar els teus propis comentarisHighlight your own comments ConfigDialog^Activitats destacades a la lnia temporal menor$Highlighted activities in minor feed ConfigDialogPActivitats destacades, excepte les meves#Highlighted activities, except mine ConfigDialog&Missatges destacatsHighlighted posts ConfigDialogDIgnorar errors d'SSL a les imatgesIgnore SSL errors in images ConfigDialogArxius d'imatge Image files ConfigDialogPInformar noms a l'autor dels "M'agrada")Inform only the author when liking things ConfigDialog Imatge no vlida Invalid image ConfigDialogNElement destacat per normes de filtrat.(Item highlighted due to filtering rules. ConfigDialog"L'element es nou. Item is new. ConfigDialogjSaltar a la lnia de nous missatges quan s'actualitzi Jump to new posts line on update ConfigDialogCostat esquerre(tabs on left side/west; RTL not affected Left side ConfigDialog.Lnies temporals menors Minor Feeds ConfigDialog*Configuraci de xarxaNetwork configuration ConfigDialogMaiNever ConfigDialogTNoves activitats a la lnia temporal menorNew activities in minor feed ConfigDialogMissatges nous New posts ConfigDialog4Estil de les notificacionsNotification Style ConfigDialogNotificacions Notifications ConfigDialog0Notificar quan es rebin:Notify when receiving: ConfigDialogZNoms per imatges inserides des de llocs web.(Only for images inserted from web sites. ConfigDialogTEs faran servir les notificacions prpies.Own notifications will be used. ConfigDialog0Contingut dels missatges Post Contents ConfigDialog(Ttols del missatges Post Titles ConfigDialogMissatgesPosts ConfigDialog\Missatges per pgina, &altres lnies temporals Posts per page, &other timelines ConfigDialogPrivacitatPrivacy ConfigDialog(Parmetres de pro&xyPro&xy Settings ConfigDialog2Configuraci del programaProgram Configuration ConfigDialogVMissatges pblics de manera &predeterminadaPublic posts as &default ConfigDialogCostat dret)tabs on right side/east; RTL not affected Right side ConfigDialogS&eleccionar... S&elect... ConfigDialog>Selecciona icona personalitzadaSelect custom icon ConfigDialog&Configurar f&iltresSet Up F&ilters ConfigDialog:Mostrar contador de carctersShow character counter ConfigDialogVMostrar informaci adicional als compartitsShow extended share information ConfigDialog0Mostrar informaci extraShow extra information ConfigDialogVMostrar informaci dels missatges eliminats"Show information for deleted posts ConfigDialog^Mostrar fragments a les :inies temporals menorsShow snippets in minor feeds ConfigDialog8Mostrar el teu avatar actualShow your current avatar ConfigDialog(Lmit dels fragments Snippet limit ConfigDialog$Safata del sistema System Tray ConfigDialogL&Tipus d'icona a la safata del sistemaSystem Tray Icon &Type ConfigDialogRIcona del sistema, si es troba disponibleSystem iconset, if available ConfigDialogfLes notificacions del sistema no estan disponibles!'System notifications are not available! ConfigDialogLa activitat es en resposta a alguna cosa feta per tu, com un comentari publicat en resposta a una de les teves notes.jThe activity is in reply to something done by you, such as a comment posted in reply to one of your notes. ConfigDialogL'activitat est relacionada amb un dels teus objectes, com per exemple quan a alg li agrada un dels teus missatges.YThe activity is related to one of your objects, such as someone liking one of your posts. ConfigDialogHLa imatge seleccionada no es vlida. The selected image is not valid. ConfigDialog<Aix s una notificaci bsicaThis is a basic notification ConfigDialogFAix es una notificaci del sistemaThis is a system notification ConfigDialog\Interval d'&actualitzaci de la lnia temporalTimeline &update interval ConfigDialog Lnies temporals Timelines ConfigDialogPart superiorTop ConfigDialogtUtilitzar nom de l'adjunt com a ttol inicial del missatge-Use attachment filename as initial post title ConfigDialog&Utilitzar amb cura.Use with care. ConfigDialogUtilitzat tamb quan es destaquen missatges dirigits a tu a les lnies temporals.DUsed also when highlighting posts addressed to you in the timelines. ConfigDialogUtilitzat tamb quan es destaquen els teus propis missatges a les lnies temporals.Aqu, tamb pots activar l'opci per publicar sempre els teus missatges com pblics de forma predeterminada. Sempre pots canviar aix en el moment de publicar.Here, you can also activate the option to always publish your posts as Public by default. You can always change that at the moment of posting. HelpWidgetSi afegeixes una persona especfica a la llista 'Per a', rebr el teu missatge a la seva pestanya de missatges directes.kIf you add a specific person to the 'To' list, they will receive your message in their direct messages tab. HelpWidget$Si fas servir una configuraci alternativa, amb alguna cosa com '--config altreconf', llavors la interfcie estar a org.nongnu.dianara_altreconf.If you use an alternate configuration, with something like '--config otherconf', then the interface will be at org.nongnu.dianara_otherconf. HelpWidgetSi es la primera vegada que utilitzes Pump.io, fes una ullada a aquesta guia:4If you're new to Pump.io, take a look at this guide: HelpWidgetSi el teu servidor no t suport HTTPS, pots utilitzar el parmetre --nohttps.KIf your server does not support HTTPS, you can use the --nohttps parameter. HelpWidgets possible adjuntar imatges, udio, vdeo i arxius generals, com documents PDF, al teu missatge.cIt is possible to attach images, audio, video, and general files, like PDF documents, to your post. HelpWidget\Recorda que hi ha molts llocs a Dianara on pots obtenir ms informaci mantenint el ratol sobre algun text o bot, i esperant a que aparegui l'indicador de funci (tooltip).Keep in mind that there are a lot of places in Dianara where you can get more information by hovering over some text or button with your mouse, and waiting for the tooltip to appear. HelpWidget$Control per teclatKeyboard controls HelpWidget0Gestionant els contactesManaging contacts HelpWidget$Lnia temporal de missatges, on veurs missatges enviats a tu especficament. Aquests missatges poden haver estat enviats tamb a altres persones.Messages timeline, where you'll see messages sent to you specifically. These messages might have been sent to other people too. HelpWidgetEls missatges nous es mostren destacats en un color diferent. Els pots marcar com llegits fent clic a qualsevol part buida del missatge.New messages appear highlighted in a different color. You can mark them as read just by clicking on any empty parts of the message. HelpWidgetPublicantPosting HelpWidget0Guia d'usuari de Pump.ioPump.io User Guide HelpWidgetConfiguraciSettings HelpWidget0La cinquena lnia temporal es la lnia temporal menor, tamb coneguda com el "Mentrestant". s visible a la banda esquerra, encara que es pot amagar. Aqu veurs activitats secundries fetes per tothom, com ara accions de comentar, marcar missatges amb "M'agrada" o seguir a gent.6LEFT SIDE should change to RIGHT SIDE on RTL languagesThe fifth timeline is the minor timeline, also known as the Meanwhile. This is visible on the left side, though it can be hidden. Here you'll see minor activities done by everyone you follow, such as comment actions, liking posts or following people. HelpWidgetLa primera vegada que arrenquis Dianara, hauries de veure la finestra de Configuraci del compte. All, introdueix la teva adrea de Pump.io com nom@servidor i prem el bot Obtenir codi de verificaci.The first time you start Dianara, you should see the Account Configuration dialog. There, enter your Pump.io address as name@server and press the Get Verifier Code button. HelpWidgetLa interfcie es troba a %1, i pots accedir-hi amb eines com %2 o %3. Ofereix mtodes com %4 i %5.lThe interface is at %1, and you can access it with tools such as %2 or %3. It offers methods like %4 and %5. HelpWidgetLa lnia temporal principal, on veurs tot el que ha publicat o compartit la gent a la que segueixes.\The main timeline, where you'll see all the stuff posted or shared by the people you follow. HelpWidgetLes accions ms comunes que es troben als mens tenen tecles de drecera escrites al costat, com F5 o Control+N.nThe most common actions found on the menus have keyboard shortcuts written next to them, like F5 or Control+N. HelpWidgetLa sisena i la setena lnies temporals tamb son lnies temporals menors, semblants al "Mentrestant", pero contenen noms activitats dirigides a tu (Mencions) i activitats fetes per tu (Accions).The sixth and seventh timelines are also minor timelines, similar to the Meanwhile, but containing only activities directly addressed to you (Mentions) and activities done by you (Actions). HelpWidgetBA continuaci, el teu navegador web habitual hauria de carregar la pgina d'autoritzaci al teu servidor Pump.io. All, haurs de copiar el codi 'VERIFIER' complet, i enganxar-ho al segon camp de Dianara. Llavors prem 'Autoritzar l'aplicaci', i quan sigui confirmat, prem 'Guardar dades'.Then, your usual web browser should load the authorization page in your Pump.io server. There, you'll have to copy the full VERIFIER code, and paste it into Dianara's second field. Then press Authorize Application, and once it's confirmed, press Save Details. HelpWidget6Hi ha set lnies temporals:There are seven timelines: HelpWidgetHi ha un camp de text a la part superior, on pots introduir directament adreces de contactes nous per seguir-los.hThere is a text field at the top, where you can directly enter addresses of new contacts to follow them. HelpWidgetAll pots gestionar tamb les llistes de persones, que s'utilitzen principalment per enviar missatges a grups de gent especfics.`There, you can also manage person lists, used mainly to send posts to specific groups of people. HelpWidgetAquestes activitats poden tenir un bot "+". Prem-ho per obrir el missatge al que fan referncia. A ms, com a molts altres llocs, pots mantenir el ratol a sobre per veure informaci rellevant a l'indicador de funci (tooltip).These activities might have a '+' button in them. Press it to open the post they're referencing. Also, as in many other places, you can hover with your mouse to see relevant information in the tooltip. HelpWidget Lnies temporals Timelines HelpWidget6Dins de la pestanya 'Vens' veurs alguns recursos per trobar gent, i tindrs la opci de veure els ltims usuaris registrats al teu servidor, directament.Under the 'Neighbors' tab you'll see some resources to find people, and have the option to browse the latest registered users from your server directly. HelpWidgetFes servir el parmetre --debug per tenir informaci addicional a la finestra de la terminal, sobre el que est fent el programa.mUse the --debug parameter to have extra information in your terminal window, about what the program is doing. HelpWidget$Usuaris per idiomaUsers by language HelpWidgetFMentre redactes una nota, prem Enter per passar del ttol al cos del missatge. A ms, prement la fletxa amunt quan siguis al principi del missatge, torna al ttol.While composing a note, press Enter to jump from the title to the message body. Also, pressing the Up arrow while you're at the start of the message, jumps back to the title. HelpWidgetTamb pots enviar un missatge directe (inicialment privat) a aquest contacte des d'aquest men.VYou can also send a direct message (initially private) to that contact from this menu. HelpWidgetTamb pots teclejar '@' i els primers carcters del nom d'un contacte per mostrar un men emergent amb opcions que coincideixin.wYou can also type '@' and the first characters of the name of a contact to bring up a popup menu with matching choices. HelpWidgetPots fer clic a qualsevol avatar als missatges, als comentaris, i a la columna "Mentrestant", i veurs un men amb algunes opcions, una de les quals s seguir o deixar de seguir a aquesta persona.You can click on any avatars in the posts, the comments, and the Meanwhile column, and you will get a menu with several options, one of which is following or unfollowing that person. HelpWidget Pots ajustar diverses coses al teu gust a la configuraci, com l'interval de temps entre actualitzacions de la lnia temporal, quants missatges per pgina vols, colors per destacar missatges, notificacions o quin aspecte tindr la icona de la safata del sistema.You can configure several things to your liking in the settings, like the time interval between timeline updates, how many posts per page you want, highlight colors, notifications or how the system tray icon looks. HelpWidgetPots fer missatges privats afegint gent especfica a aquestes llistes, i desmarcant les opcions Pblic i Seguidors.~You can create private messages by adding specific people to these lists, and unselecting the Followers or the Public options. HelpWidgetPots trobar una llista amb alguns usuaris de Pump.io i altres dades aqu:GYou can find a list with some Pump.io users and other information here: HelpWidgetFPots publicar notes fent clic al camp de text de la part superior de la finestra o prement Control+N. Afegir un ttol al missatge s opcional, per molt recomanable, ja que ajudar a identificar millor les referncies al teu missatge a la lnia temporal menor, notificacions per e-mail, etc.You can post notes by clicking in the text field at the top of the window or by pressing Control+N. Setting a title for your post is optional, but highly recommended, as it will help to better identify references to your post in the minor feed, e-mail notifications, etc. HelpWidgetPots veure les llistes de gent que segueixes, i que et segueix, a la pestanya Contactes.UYou can see the lists of people you follow, and who follow you from the Contacts tab. HelpWidgetPots seleccionar qui veur el teu missatge fent servir els botons 'Per a' i 'Cc'.EYou can select who will see your post by using the To and Cc buttons. HelpWidgetPots fer servir el parmetre --config per executar el programa amb una configuraci diferent. Aix pot ser til per utilitzar dos o ms comptes diferents. Fins i tot pots executar dues instncies de Dianara a la vegada.You can use the --config parameter to run the program with a different configuration. This can be useful to use two or more different accounts. You can even run two instances of Dianara at the same time. HelpWidget^Pots fer servir el bot Format per afegir format al text, com ara negreta o cursiva. Algunes d'aquestes opcions necessiten que el text sigui seleccionat abans d'utilitzar-les.You can use the Format button to add formatting to your text, like bold or italics. Some of these options require text to be selected before they are used. HelpWidget8Hauries de fer una ullada a la finestra 'Configuraci del programa', al men Configuraci - Configurar Dianara. All trobars diverses opcions interessants.You should take a look at the Program Configuration window, under the Settings - Configure Dianara menu. There are several interesting options there. HelpWidget&Tancar&Close ImageViewer&&Reiniciar animaci&Restart Animation ImageViewer&Guardar com... &Save As... ImageViewerTots els arxius All files ImageViewer(Tancar visualitzador Close Viewer ImageViewer0Error guardant la imatgeError saving image ImageViewer ImatgeImage ImageViewerArxius d'imatge Image files ImageViewer*Guardar imatge com...Save Image As... ImageViewer"Guardar imatge... Save Image... ImageViewerHi ha hagut un problema al guardar %1. El nom de l'arxiu hauria d'acabar amb l'extensi .jpg o .png.UThere was a problem while saving %1. Filename should end in .jpg or .png extensions. ImageViewer &Afegir a llista &Add to List ListsManager:&Esborrar llista seleccionada&Delete Selected List ListsManager&No&No ListsManager &Eliminar membre&Remove Member ListsManager&Si&Yes ListsManager &S, eliminar-la&Yes, delete it ListsManagerAfegir mem&bre Add Mem&ber ListsManager&Afegir nova &llista Add New &List ListsManagerHEsts segur de que vols eliminar %1?#Are you sure you want to delete %1? ListsManagerhEsts segur de que vols treure a %1 de la llista %2?4Are you sure you want to remove %1 from the %2 list? ListsManagerCrear ll&ista Create L&ist ListsManagerMembresMembers ListsManagerNomName ListsManager8Treure persona de la llista?Remove person from list? ListsManagerFEscriu un nom per la nova llista...Type a name for the new list... ListsManagerFEscriu una descripci opcional aqu!Type an optional description here ListsManager,AVS: Eliminar llista?WARNING: Delete list? ListsManager&Tancar&Close LogViewer*Esborrar el &registre Clear &Log LogViewerRegistreLog LogViewer%1 bytes%1 bytes MainWindow%1 eliminats. %1 deleted. MainWindow%1 filtrats.%1 filtered out. MainWindow%1 filtrades.plural, several activities%1 filtered out. MainWindow%1 destacats.%1 highlighted. MainWindow%1 destacades.plural, refers to activities%1 highlighted. MainWindow2%1 ms pendents de rebre.%1 more pending to receive. MainWindow2%1 ms pendents de rebre.plural, several activities%1 more pending to receive. MainWindow&Compte&Account MainWindow&Activitat &Activity MainWindow&&Configurar Dianara&Configure Dianara MainWindow&Contactes &Contacts MainWindow(&Filtres i destacats&Filters and Highlighting MainWindow Aj&uda&Help MainWindow"&Ocultar finestra &Hide Window MainWindow&Registre&Log MainWindow&Missatges &Messages MainWindow&No&No MainWindow$&Publicar una nota &Post a Note MainWindow&Sortir&Quit MainWindow&Sessi&Session MainWindow"&Mostrar finestra &Show Window MainWindowLnia &temporal &Timeline MainWindow&Barra d'eines&Toolbar MainWindow &Veure&View MainWindow.&S, tancar el programa&Yes, close the program MainWindow$'%1' actualitzada. '%1' updated. MainWindow1 eliminat. 1 deleted. MainWindow1 filtrat.1 filtered out. MainWindow1 filtrada. singular, refers to one activity1 filtered out. MainWindow1 destacat.1 highlighted. MainWindow1 destacada.singular, refers to an activity1 highlighted. MainWindow.1 ms pendent de rebre.1 more pending to receive. MainWindow.1 ms pendent de rebre.singular, 1 activity1 more pending to receive. MainWindowSobre &DianaraAbout &Dianara MainWindowSobre Dianara About Dianara MainWindow A ms:Also: MainWindowDAuto-actualitzar lnies &temporalsAuto-update &Timelines MainWindowBAuto-actualitzacions desactivadesAuto-updating disabled MainWindow<Auto-actualitzacions activadesAuto-updating enabled MainWindow&Ajuda bsica Basic &Help MainWindowTFes clic aqu per configurar el teu compte$Click here to configure your account MainWindowBFes clic per editar el teu perfilClick to edit your profile MainWindowbTancant a causa de que l'entorn s'est aturant...+Closing due to environment shutting down... MainWindowdDianara no es pot ocultar a la safata del sistema.,Dianara cannot be hidden in the system tray. MainWindowbDianara es un client de xarxa social per pump.io..Dianara is a pump.io social networking client. MainWindow Dianara es troba llicenciat sota la llicncia GNU GPL, i utilitza algunes icones Oxygen: http://www.oxygen-icons.org/ (Llicncia LGPL)wDianara is licensed under the GNU GPL license, and uses some Oxygen icons: http://www.oxygen-icons.org/ (LGPL licensed) MainWindow Dianara iniciat.Dianara started. MainWindow:Realment vols tancar Dianara?$Do you really want to close Dianara? MainWindowJVols tancar el programa completament?,Do you want to close the program completely? MainWindowEditar &perfil Edit &Profile MainWindowHTraducci al catal per JanKusanagi.#English translation by JanKusanagi. MainWindowbIntrodueix la contrasenya pel teu servidor proxy:)Enter the password for your proxy server: MainWindow<Error emmagatzemant la imatge!Error storing image! MainWindow&Favorits Favor&ites MainWindow$&Pantalla completa Full &Screen MainWindow Inicialitzant...Initializing... MainWindow0ltima actualitzaci: %1Last update: %1 MainWindowEnlla a: %1 Link to: %1 MainWindowFLlista d'alguns &usuaris de Pump.ioList of Some Pump.io &Users MainWindow.Marcar tot com a llegitMark All as Read MainWindow6Marcant tot com a llegit...Marking everything as read... MainWindowHMissatges enviats explcitament a tuMessages sent explicitly to you MainWindow@Activitats menors dirigides a tu!Minor activities addressed to you MainWindowActivitats menors fetes per tothom, com per exemple respostes a missatgesContrasenya de proxy necessriaProxy password required MainWindowFWeb de l'estat de la &xarxa Pump.ioPump.io &Network Status Website MainWindow2&Guia d'usuari de Pump.ioPump.io User &Guide MainWindowSortir?Quit? MainWindowVS'han rebut %1 activitats anteriors a '%2'.%Received %1 older activities in '%2'. MainWindowTS'han rebut %1 missatges anteriors a '%2'. Received %1 older posts in '%2'. MainWindow(Informar d'un &error Report a &Bug MainWindow,Funcionant amb Qt v%1.Running with Qt v%1. MainWindow&Configuraci S&ettings MainWindow@Mostrar l'auxiliar de benvingudaShow Welcome Wizard MainWindow$Tancant Dianara...Shutting down Dianara... MainWindowCuadre &lateral Side &Panel MainWindow<Alguns &consells sobre Pump.ioSome Pump.io &Tips MainWindowIniciant actualitzaci automtica de lnies temporals, una vegada cada %1 minuts.>Starting automatic update of timelines, once every %1 minutes. MainWindow&Barra d'estat Status &Bar MainWindowjAturant actualitzaci automtica de lnies temporals.'Stopping automatic update of timelines. MainWindowjLa icona de la safata del sistema no est disponible."System tray icon is not available. MainWindowGrcies a tots els 'testers', traductors i empaquetadors, que ajuden a fer Dianara millor!SThanks to all the testers, translators and packagers, who help make Dianara better! MainWindow6La lnia temporal principalThe main timeline MainWindowLa gent a la que segueixes, la que et segueix, i les teves llistes de personesEThe people you follow, the ones who follow you, and your person lists MainWindow6Hi han %1 activitats noves.There are %1 new activities. MainWindow2Hi han %1 missatges nous.There are %1 new posts. MainWindow.Hi ha 1 activitat nova.There is 1 new activity. MainWindow*Hi ha 1 missatge nou.There is 1 new post. MainWindowJLnia temporal actualitzada a les %1.Timeline updated at %1. MainWindowBarra d'einesToolbar MainWindow,Total de missatges: %1Total posts: %1 MainWindowActualitzar %1 Update %1 MainWindow"Visitar lloc &webVisit &Website MainWindowfAmb Dianara pots veure les teves lnies temporals, crear nous missatges, penjar fotos i multimdia, interactuar amb els missatges, gestionar els teus contactes i seguir gent nova.With Dianara you can see your timelines, create new posts, upload pictures and other media, interact with posts, manage your contacts and follow new people. MainWindowPEsts redactant una nota o un comentari.&You are composing a note or a comment. MainWindowHas configurat un servidor proxy amb autenticaci, pero la contrasenya no est definida.TYou have configured a proxy server with authentication, but the password is not set. MainWindowPEl teu compte Pump.io no est configurat&Your Pump.io account is not configured MainWindowPEl teu compte no est configurat encara.#Your account is not configured yet. MainWindow8La teva biografia est buidaYour biography is empty MainWindow6Els missatges que t'agradenYour favorited posts MainWindow2Els teus propis missatgesYour own posts MainWindow$Rebre %1 ms noves Get %1 newer MinorFeedFObtenir activitats menors anteriorsGet previous minor activities MinorFeed(Activitats anteriorsOlder Activities MinorFeedNEncara no hi ha activitats per mostrar.$There are no activities to show yet. MinorFeed Cc: %1Cc: %1 MinorFeedItem:Obrir el missatge referenciatOpen referenced post MinorFeedItemPer a: %1To: %1 MinorFeedItemUtilitzant %1Using %1 MinorFeedItem bytesbytes MiscHelpers&Cancellar&Cancel PageSelector&PrimeraAs in: first page&First PageSelector &Anar&Go PageSelectorltim&aAs in: last page&Last PageSelectorSaltar a pgina Jump to page PageSelectorMs novesAs in: newer pagesNewer PageSelectorMs antiguesAs in: older pagesOlder PageSelector"Nmero de pgina: Page number: PageSelector&Cancellar&Cancel PeopleWidgetCe&rcar:&Search: PeopleWidget>Afegir un contacte a una llistaAdd a contact to a list PeopleWidget@Escriu aqu un nom per buscar-ho"Enter a name here to search for it PeopleWidget%1 comentaris %1 commentsPost$Els hi agrada a %1 %1 like thisPostLi agrada a %1%1 likesPostLi agrada a %1 %1 likes thisPost$%1 membres al grup%1 members in the groupPost(%1 ha compartit aix%1 shared thisPost*%1 han compartit aix#%1 = Names for more than one person%1 shared thisPost&Tancar&ClosePost&No&NoPost &S, eliminar-ho&Yes, delete itPost"&S, compartir-ho&Yes, share itPost6&S, deixar de compartir-ho&Yes, unshare itPost1 comentari 1 commentPostLi agrada a 11 likePostbEsts segur de que vols eliminar aquest missatge?*Are you sure you want to delete this post?Postudio adjuntAttached AudioPostArxiu adjunt Attached FilePostVdeo adjuntAttached VideoPostCcCcPostbFes clic a la imatge per veure-la a mida completa&Click the image to see it in full sizePostNFes clic per descarregar l'arxiu adjunt Click to download the attachmentPostComentarCommentPostZCopiar l'enlla del missatge al porta-retallsCopy post link to clipboardPostBNo s'ha pogut carregar la imatge!Couldn't load image!PostEliminarDeletePostBVols compartir el missatge de %1?Do you want to share %1's post?PostVVols deixar de compartir el missatge de %1?!Do you want to unshare %1's post?Post EditarEditPostEditat: %1 Edited: %1Post0Esborrar aquest missatgeErase this postPostRSi selecciones part del text, ser citat.+If you select some text, it will be quoted.Post`La imatge s animada. Fes clic per reproduir-la.'Image is animated. Click on it to play.PostAInPostUnir-se al grup Join GroupPostM'agradaLikePost@Dir que t'agrada aquest missatgeLike this postPost&Carregant imatge...Loading image...PostModificat el %1Modified on %1Post2Modificar aquest missatgeModify this postPost6Normalitzar colors del textNormalize text colorsPostDObrir el missatge al navegador webOpen post in web browserPostXObrir el missatge pare, al que aquest respon/Open the parent post, to which this one repliesPostPareParentPostMissatgePostPostPublicat el %1 Posted on %1Post8Respondre a aquest missatge.Reply to this post.PostCompartirSharePost&Compartir missatge? Share post?Post`Compartir aquest missatge amb els teus contactes"Share this post with your contactsPost"Compartit %1 copsShared %1 timesPostCompartit el %1 Shared on %1Post Compartit un cop Shared oncePost Per aToPost TipusTypePostJa no m'agradaUnlikePost&Deixar de compartirUnsharePost@Deixar de compartir el missatge? Unshare post?PostFDeixar de compartir aquest missatgeUnshare this postPostUtilitzant %1Using %1PostA travs de %1Via %1Post0AVS: Eliminar missatge?WARNING: Delete post?PostT'agrada aix You like thisPost&Bio&Bio ProfileEditor&Cancellar&Cancel ProfileEditorCiuta&t &Hometown ProfileEditor&Guardar perfil &Save Profile ProfileEditorTots els arxius All files ProfileEditor AvatarAvatar ProfileEditor$Canviar &avatar...Change &Avatar... ProfileEditor$Canviar &e-mail...Change &E-mail... ProfileEditor E-mailE-mail ProfileEditor&Nom complet Full &Name ProfileEditorArxius d'imatge Image files ProfileEditor Imatge no vlida Invalid image ProfileEditorSense definirNot set ProfileEditor Editor de perfilProfile Editor ProfileEditor4Selecciona imatge d'avatarSelect avatar image ProfileEditorHLa imatge seleccionada no es vlida. The selected image is not valid. ProfileEditorAquesta s l'adrea d'e-mail associada al teu compte, per coses com ara notificacions i recuperaci de la contrasenyaoThis is the e-mail address associated with your account, for things such as notifications and password recovery ProfileEditor<Aquesta es la teva adrea PumpThis is your Pump address ProfileEditor8Aquest es el teu nom visibleThis is your visible name ProfileEditorID Webfinger Webfinger ID ProfileEditor&Cancellar&Cancel ProxyDialog&Servidor &Hostname ProxyDialog &Port&Port ProxyDialog&Guardar&Save ProxyDialog&Usuari&User ProxyDialog,No fer servir un proxyDo not use a proxy ProxyDialogNota: La contrasenya no s'emmagatzema de forma segura. Si ho desitges, pots deixar el camp buit, i se't demanar la contrasenya a l'inici.Note: Password is not stored in a secure manner. If you wish, you can leave the field empty, and you'll be prompted for the password on startup. ProxyDialogCon&trasenya Pass&word ProxyDialog&Tipus de proxy Proxy &Type ProxyDialog*Configuraci de proxyProxy Configuration ProxyDialog.Utilitzar &autenticaciUse &Authentication ProxyDialog:El teu nom d'usuari pel proxyYour proxy username ProxyDialog.%1 KiB de %2 KiB pujats%1 KiB of %2 KiB uploaded Publisher>&Cancellar, tornar al missatge&Cancel, go back to the post PublisherT&No, publicar noms per als meus seguidors&No, post to my followers only Publisher$&S, fer-ho pblic&Yes, make it public Publisher&Afegir...Ad&d... PublisherlAfegeix un ttol breu per al missatge aqu (recomanat)1Add a brief title for the post here (recommended) PublisherTots els arxius All files Publisher udioAudio PublisherBNo s'ha escollit un arxiu d'udioAudio file not set PublisherArxius d'udio Audio files PublisherCancellarCancel Publisher\Cancellar l'adjunt i tornar a una nota normal4Cancel the attachment, and go back to a regular note Publisher,Cancellar el missatgeCancel the post Publisher Cc...Cc... Publisher(Actualment, Dianara limita les pujades d'arxius a 10 MiB per missatge, per a evitar possibles problemes d'emmagatzematge, o de xarxa, als servidors.yDianara currently limits file uploads to 10 MiB per post, to prevent possible storage or network problems in the servers. Publisher|Vols fer el missatge pblic en comptes de noms per seguidors?>Do you want to make the post public instead of followers-only? Publisher Editant missatge Editing post Publisher4Error: Ja s'est redactantError: Already composing Publisher*L'arxiu s massa granFile is too big Publisher:No s'ha seleccionat un arxiu.File not selected. Publisher2No s'ha escollit un arxiu File not set PublisherVTrobar l'arxiu d'audio a les teves carpetes#Find the audio file in your folders PublisherFTrobar l'arxiu a les teves carpetesFind the file in your folders PublisherFTrobar la foto a les teves carpetes Find the picture in your folders PublisherHTrobar el vdeo a les teves carpetesFind the video in your folders PublisherSeguidors Followers PublisherZPrem Control+Enter per publicar amb el teclat+Hit Control+Enter to post with the keyboard PublisherSi publiques d'aquesta manera, ning podr veure el teu missatge.?If you post like this, no one will be able to see your message. PublisherpIgnorant petici de nova nota des d'una altra aplicaci.3Ignoring new note request from another application. PublisherArxius d'imatge Image files Publisher,Arxiu d'udio no vlidInvalid audio file PublisherArxiu no vlid Invalid file Publisher Imatge no vlida Invalid image Publisher.Arxiu de vdeo no vlidInvalid video file PublisherLlistesLists PublisherRNota comenada des d'una altra aplicaci.&Note started from another application. Publisher AltresOther PublisherPersones... People... PublisherFotoPicture Publisher2No s'ha escollit una fotoPicture not set PublisherPublicarPost Publisher,El missatge est buit.Post is empty. PublisherXHa fallat la publicaci. Torna-ho a provar.Posting failed. Try again. PublisherPublicant... Posting... Publisher PblicPublic Publisher TreureRemove PublisherResoluci Resolution Publisher8Seleccionar arxiu d'udio...Select Audio File... Publisher(Seleccionar arxiu...Select File... Publisher&Seleccionar foto...Select Picture... Publisher(Seleccionar vdeo...Select Video... Publisher6Selecciona un arxiu d'udioSelect one audio file Publisher&Selecciona un arxiuSelect one file Publisher*Selecciona una imatgeSelect one image Publisher8Selecciona un arxiu de vdeoSelect one video file Publisher`Selecciona qui rebr una cpia d'aquest missatge'Select who will get a copy of this post PublisherHSelecciona qui veur aquest missatgeSelect who will see this post PublisherAfegir un ttol ajuda a fer el contingut del "Mentrestant" ms informatiu>Setting a title helps make the Meanwhile feed more informative PublisherCom que el que ests pujant s una imatge, podries reduir-la una mica o guardarla en un format ms comprimit, com JPG.sSince you're uploading an image, you could scale it down a little or save it in a more compressed format, like JPG. PublisherMidaSize Publisher.Lamentem les molsties.Sorry for the inconvenience. PublisherJEl format d'udio no es pot detectar.$The audio format cannot be detected. PublisherHEl tipus d'arxiu no es pot detectar.!The file type cannot be detected. Publisher No es pot detectar el format de la imatge. Potser l'extensi est equivocada, com una imatge GIF reanomenada a imatge.jpg o semblant.tThe image format cannot be detected. The extension might be wrong, like a GIF image renamed to image.jpg or similar. PublisherLEl format de vdeo no es pot detectar.$The video format cannot be detected. PublisherAix s una mesura temporal, ja que els servidors no poden establir els seus propis lmits encara.OThis is a temporary measure, since the servers cannot set their own limits yet. Publisher TtolTitle PublisherPer a...To... Publisher TipusType PublisherActualitzarUpdate PublisherActualitzant... Updating... PublisherHPujar multimdia, com fotos o vdeos%Upload media, like pictures or videos Publisher VdeoVideo PublisherArxius de vdeo Video files Publisher2No s'ha escollit un vdeo Video not set Publisher<Avs: Encara no tens seguidors"Warning: You have no followers yet PublisherNo pots crear un missatge per a %1 en aquest moment, perque ja s'est redactant un missatge.YYou can't create a message for %1 at this time, because a post is already being composed. PublisherNo pots editar un missatge en aquest moment, perque ja s'est redactant un missatge.MYou can't edit a post at this time, because a post is already being composed. PublisherEsts intentant publicar noms per als teus seguidors, per no tens cap seguidor encara.SYou're trying to post to your followers only, but you don't have any followers yet. Publisher^S'ha afegit a %1 (%2) a la llista correctament.#%1 (%2) added to list successfully.PumpControllerdS'ha eliminat a %1 (%2) de la llista correctament.'%1 (%2) removed from list successfully.PumpController%1 intents %1 attemptsPumpController*%1 comentaris rebuts.%1 comments received.PumpControllerS'ha publicat '%1' correctament. Actualitzant contingut del missatge...3%1 published successfully. Updating post content...PumpController1 intent 1 attemptPumpController$1 comentari rebut.1 comment received.PumpControllerAccionsActionsPumpControllerActivitatActivityPumpController&Afegint elements...Adding items...PumpControllerFAfegint una persona a una llista...Adding person to list...PumpControllerS'han rebut totes les dades inicials. Inicialitzaci completada.3All initial data received. Initialization complete.PumpControllerFAplicaci autoritzada correctament.$Application authorized successfully.PumpController(Error d'autoritzaciAuthorization errorPumpController|Autoritzat per fer servir el compte %1. Rebent dades inicials.3Authorized to use account %1. Getting initial data.PumpController:Avatar publicat correctament.Avatar published successfully.PumpControllerAvatar penjat.Avatar uploaded.PumpController,Sollicitud incorrecta Bad RequestPumpControllerFComentari %1 publicat correctament.Comment %1 posted successfully.PumpControllerLComentari %1 actualitzat correctament. Comment %1 updated successfully.PumpControllerCreant grup...Creating group...PumpController8Creant llista de persones...Creating person list...PumpController>Esborrant llista de persones...Deleting person list...PumpController,E-mail actualitzat: %1E-mail updated: %1PumpController*Error connectant a %1Error connecting to %1PumpControllerFavorits FavoritesPumpController>Arxiu descarregat correctament.File downloaded successfully.PumpController`Arxiu penjat correctament. Publicant missatge....File uploaded successfully. Posting message...PumpControllerSeguidors FollowersPumpControllerSeguint FollowingPumpController>Seguint a %1 (%2) correctament.Following %1 (%2) successfully.PumpControllerProhibit ForbiddenPumpControllerPTemps d'espera de la passarella exceditGateway TimeoutPumpControllerRebent '%1'...Getting '%1'...PumpControllerlRebent identificador d'autoritzaci (token) d'OAuth...Getting OAuth token...PumpController@Rebent una llista de persones...Getting a person list...PumpController(Rebent comentaris...Getting comments...PumpController"Rebent "likes"...Getting likes...PumpController>Rebent llista de 'Seguidors'...Getting list of 'Followers'...PumpController:Rebent llista de 'Seguint'...Getting list of 'Following'...PumpControllerNRebent llista de llistes de persones...Getting list of person lists...PumpControllerBRebent usuaris del servidor %1...Getting site users for %1...PumpController Ja no disponibleGonePumpController6Grup %1 creat correctament.Group %1 created successfully.PumpControllerHS'ha entrat al grup %1 correctament.Group %1 joined successfully.PumpControllerError HTTP HTTP errorPumpControllerError internInternal Server ErrorPumpController&Unint-se al grup...Joining group...PumpController&Sortint del grup...Leaving group...PumpControllerJS'ha sortit del grup %1 correctament.Left the %1 group successfully.PumpController$"M'agrada" rebuts.Likes received.PumpController<Llista d'usuaris de %1 rebuda.List of %1 users received.PumpControllerTLlista de 'seguidors' completament rebuda.(List of 'followers' completely received.PumpControllerPLlista de 'seguint' completament rebuda.(List of 'following' completely received.PumpController6Llista de 'llistes' rebuda.List of 'lists' received.PumpControllerCarregant imatge externa de %1 tot i amb els errors SSL, com s'ha configurat...ILoading external image from %1 regardless of SSL errors, as configured...PumpControllerMentrestant MeanwhilePumpControllerMencionsMentionsPumpController>Missatge eliminat correctament.Message deleted successfully.PumpControllerhMissatge marcat o desmarcat "M'agrada" correctament.&Message liked or unliked successfully.PumpControllerMissatgesMessagesPumpController&Mogut permanentmentMoved PermanentlyPumpController$Mogut temporalmentMoved TemporarilyPumpControllerNo trobat Not FoundPumpControllerNo implementatNot ImplementedPumpControllerdError de OAuth mentre s'autoritzava a l'aplicaci.*OAuth error while authorizing application.PumpController>Error de compatibilitat d'OAuthOAuth support errorPumpControllerPPart de la llista de 'seguidors' rebuda.%Partial list of 'followers' received.PumpControllerLPart de la llista de 'seguint' rebuda.%Partial list of 'following' received.PumpControllerXLlista de persones '%1' creada correctament.&Person list '%1' created successfully.PumpControllerTLlista de persones esborrada correctament.!Person list deleted successfully.PumpController4Llista de persones rebuda.Person list received.PumpControllerDMissatge %1 publicat correctament.Post %1 published successfully.PumpControllerJMissatge %1 actualitzat correctament.Post %1 updated successfully.PumpControllerLMissatge de %1 compartit correctament.Post by %1 shared successfully.PumpControllerPerfil rebut.Profile received.PumpController&Perfil actualitzat.Profile updated.PumpController$Error de QOAuth %1QOAuth error %1PumpControllerPreparat.Ready.PumpController S'ha rebut '%1'.Received '%1'.PumpControllerFTreient una persona d'una llista...Removing person from list...PumpController@Errors d'SSL a la connexi a %1!SSL errors in connection to %1!PumpController(Servei no disponibleService UnavailablePumpControllerXAlgunes dades inicials no s'han rebut desprs de diversos intents. Alguna cosa pot estar fallant al teu servidor. Es possible que puguis utilitzar el servei amb normalitat.Some initial data was not received after several attempts. Something might be wrong with your server. You might still be able to use the service normally.PumpControllerAlgunes dades inicials no s'han rebut. Reiniciant la inicialitzaci...@Some initial data was not received. Restarting initialization...PumpControllerpEncara s'est esperant el perfil. Intentant-ho de nou...*Still waiting for profile. Trying again...PumpControllerZS'ha deixat de seguir a %1 (%2) correctament.'Stopped following %1 (%2) successfully.PumpControllerLa aplicaci encara no es troba registrada amb el teu servidor. Registrant...FThe application is not registered with your server yet. Registering...PumpControllerEls comentaris d'aquest missatge no es poden carregar a causa de que falten dades al servidor.NThe comments for this post cannot be loaded due to missing data on the server.PumpController>No hi ha cap compte autoritzat.There is no authorized account.PumpControllerHi ha hagut un error de OAuth mentre s'intentava obtenir un identificador d'autoritzaci (token).EThere was an OAuth error while trying to get the authorization token.PumpControllerLnia temporalTimelinePumpControllerNo autoritzat UnauthorizedPumpControllerDCodi d'error HTTP no gestionat: %1Unhandled HTTP error code %1PumpController\Missatge sense ttol %1 publicat correctament.(Untitled post %1 published successfully.PumpControllerbMissatge sense ttol %1 actualitzat correctament.&Untitled post %1 updated successfully.PumpControllerPujant %1 Uploading %1PumpController.Lnia temporal d'usuari User timelinePumpControllerPEsperant per la contrasenya del proxy...Waiting for proxy password...PumpControllerProbablement necessitis installar el connector d'OpenSSL per QCA: %1, %2 o similar.KYou probably need to install the OpenSSL plugin for QCA: %1, %2 or similar.PumpControllerSembla que la teva installaci de QOAuth, una biblioteca utilitzada per Dianara, no te compatibilitat amb HMAC-SHA1._Your installation of QOAuth, a library used by Dianara, doesn't seem to have HMAC-SHA1 support.PumpController%1 usuaris a %2%1 users in %2 SiteUsersList Tancar la llista Close list SiteUsersListNRebre llista d'usuaris del teu servidor"Get list of users from your server SiteUsersListCarregant... Loading... SiteUsersList@Ms recursos per trobar usuaris:More resources to find users: SiteUsersList^Servei PPump de cerca d'usuaris a inventati.org*PPump user search service at inventati.org SiteUsersList@Pgina wiki 'Usuaris per idioma'Wiki page 'Users by language' SiteUsersListPots rebre una llista dels usuaris ms nous registrats al teu servidor fent clic al bot de sota.^You can get a list of the newest users registered on your server by clicking the button below. SiteUsersListn%1 missatges ms pendents per la propera actualitzaci.&%1 more posts pending for next update.TimeLine,%1 missatges en total.%1 posts in total.TimeLine|No es pot actualitzar '%1' perqu s'est editant un comentari.E'%1' cannot be updated because a comment is currently being composed.TimeLine4Lnia temporal d'activitatActivity TimelineTimeLineQuan el procs estigui llest, el teu perfil i lnies temporals haurien d'actualitzar-se automticament.RAfter the process is done, your profile and timelines should update automatically.TimeLineFes clic aqu o prem Control+G per saltar a una pgina especfica8Click here or press Control+G to jump to a specific pageTimeLine>Fes clic aqu per rebre'ls ara.Click here to receive them now.TimeLineHDianara s un client <b>Pump.io</b>.#Dianara is a Pump.io client.TimeLineBloc de DianaraDianara's blogTimeLineHLnia temporal de missatges directesDirect Messages TimelineTimeLine4Lnia temporal de favoritsFavorites TimelineTimeLineEn primer lloc, configura el teu compte des del men <b>Configuraci - Compte</b>.FFirst, configure your account from the Settings - Account menu.TimeLinenAqu veurs els missatges dirigits especficament a tu.4Here, you'll see posts specifically directed to you.TimeLineSi encara no tens un compte Pump, pots aconseguir-ne un a la segent adrea, per exemple:]If you don't have a Pump account yet, you can get one at the following address, for instance:TimeLineCarregant... Loading...TimeLineMs nousNewerTimeLineEl ms nouNewestTimeLineMs anticsOlderTimeLine Pgina %1 de %2.Page %1 of %2.TimeLineRMissatges i comentaris que t'han agradat. Posts and comments you've liked.TimeLinebPrem <b>F1</b> si vols obrir la finestra d'ajuda.4Press F1 if you want to open the Help window.TimeLine0Guia d'usuari de Pump.ioPump.io User GuideTimeLineDemanant... Requesting...TimeLineBMostrant %1 missatges per pgina.Showing %1 posts per page.TimeLinePren-te un moment per fer una ullada als mens i la finestra de Configuraci.DTake a moment to look around the menus and the Configuration window.TimeLine$No hi ha missatgesThere are no postsTimeLineLHi ha indicadors de funci (tooltips) per tot arreu, pel que si mantens el ratol sobre un bot o un camp de text, s probable que vegis alguna informaci addicional.There are tooltips everywhere, so if you hover over a button or a text field with your mouse, you'll probably see some extra information.TimeLine&Benvingut a DianaraWelcome to DianaraTimeLineTamb pots omplir la teva informaci de perfil i foto des del men <b>Configuraci - Editar perfil</b>.\You can also set your profile data and picture from the Settings - Edit Profile menu.TimeLineLAqu veurs els teus propis missatges.You'll see your own posts here.TimeLineFa %1 dies %1 days ago TimestampFa %1 hores %1 hours ago TimestampFa %1 minuts%1 minutes ago TimestampFa %1 mesos %1 months ago TimestampFa %1 anys %1 years ago TimestampFa un minut A minute ago TimestampFa un mes A month ago TimestampFa un any A year ago TimestampFa una hora An hour ago TimestampEn el futur In the future Timestamp(Hora/data no vlida!Invalid timestamp! TimestampAra mateixJust now TimestampAhir Yesterday Timestamp%1 missatges%1 posts UserPosts&Tancar&Close UserPostsBError carregant la lnia temporalError loading the timeline UserPostsCarregant... Loading... UserPostsMissatges de %1 Posts by %1 UserPosts S'ha rebut '%1'.Received '%1'. UserPostsA dianara-v1.3.2/translations/dianara_es.qm000644 000764 000764 00000352710 12614520445 020055 0ustar00janjan000000 000000 cOM<{<+6@)D/KBjH5wH:*HHFIaHK.MeF Nx6NOtP7P7RS STNTV* VVTWjRWL`X Xƥ#NY3 Zy%5[ %cY[!c\\dj  knq׎VwjgyT9Xybvzz><6h]N'/cR>տG}lP^\."4eD̸C\ՅI2I5 QҥH~h7[*"uWNz>f>j15 y_u>dMݣ7ZsN($YGn&ķ-!`0e<#<DInTM$&P3QE2jRS:V8\qlo,p`N=#,0,r]h>_J^MWt@ m ENq8Σ% 2YI $/XQ UZ*)%%Zb3x>KL==QRxN@dhB~pH6tɥSv3w[Ss?JCb^v zjt"6V666x6%6FO o} nMU}!Kƭ>Z4w?VϠN) ӻ ؞rdnwr53J8Y3#yJq: H L r-}|mbi vSNJ$1#$12l(JԽ.s0cR0c6O6v6'w470'ihEA9M.\XR@ ZN ZBdVi6rpijC)l#,sC$Xwh3x2zq{eB}Hz/=.Qv&kNns^nKBmbR%¿a!¿mÌP^Qjnq"]q/޷(?si_#n $n%|?[3ʳg 14` M#n>$L6U U>B*?P Tj `3$a$gUUr) s|}y::h@79<| %8U>/]AI$IvIGXz'  6Ai, ,+<3nԁj^\cO}mI4Pͣ33g3gj.ր vR}B@|z/r76WphFf 3<#1x,z4{>1KTKTlLܤRMsRtEV|4E]L.k]L{abcfy:h9~ihT2j{s5u_yrzP{7{V|7}Mw)j,.G{FWVVh9 h>_[4L>T'tHGw:!4BúXpE\wp0#055ƨc26on:uvKNUأRe""!*+#-^4uK4u:4uS89$WM<.8<.f<.j`=*At /CBGmbBGIM?$RQ+T$>VхkZPZzZf;3vf3ghCmnrp{0or7TuRy<^a/ N2n?2S9N`Gz,1̐YjV4!ґiڑDڱ1wU xUCK#DF03=1;53;Xa<J<<::<R;WQc{g0j&lqNEp'rt({k}GSjžoZdmAENTRO{uxȯB9ҕ<>y nM{NoyEi#yQ; iA8nnjYE%3"#N ")2K42?-6~~FMrc\]ere7skZ$s#WsnT|2gn|/raU6w'NwejB>KBV/q 6 L| 7w2 Sg_ ^nf r% st w>sOh || ~6Z  tD: 4 r^ C~ ~ eg  p ` + " o  b fZz ʞ U7 pa & ~ Ԭ] [q t* SJ hC@ NfZ A A6 Ͱ ~ n$? o >F !/r #| (O ?mW @=c @Cci Hk Tu TGlC TG [dtu ]B cR d 4'/ d< d<%? d<*` pO1 q\. v'e0 z3.a _> . gu O I Iv I= Iy I} I ? I"e I7 I> )- ~ I m{ 9 y, NkO o%y d tC# V5 ^ '_+ t= ՁE 3Go ֓,a N %X TB mh ؄H  0,< :&d ~  >~H ~R ~[ ~ V c5 \" ~ "e;i " ,i4) -֮ 05 5<h 7"D <خx =/n @4 A# ED!. XG Ya) bX& bXL eTC f( f*2 mmT& o> y[>P |6B] P/W !u q3k SB| #X #L Vp O G Q| D )Z C U " sd e 5 jo OCz\ أ4 y n C@ ` 2 Ғ > (=). - U8< / 68- =x BY E Y5p [cP ]ӡZ `u d.Q{ eM[p j8| {^ {; I  N rww V x } q; # s ͡8 =Y ( Б*A P < yu^ |SH ?k w4t ae =X  Е #pn_ )r . BK G= Nn V- _Pdb b e&S h1k iFCu} 1 @$ |F / K ># ^c q^ ˽+2 } b; (I  Ș `( ZV V3N20T,+jCg/9D9cR/0 RT3b$Xc^[gO@OlNl?lull8lFmhnnm}u$8T8T8T&I>Qi'E4N*!!zpTX'v@Z^U~A%ZfF 4F!.U>pO/ylR #ut`^_^`iwz9'5%a'5%`'5%'E)f+<,0*A  BtnB}~;CE.F@JNCPdT? _}16eY.uX`Ƹci0D2^++/~nv=s.Cz^Ka~d<؞¹e8$YWe%~~dzPe"TڨdU9B#pޝyi%1 de %2%1 by %2 ASActivityPblicoPublic ASActivityArtculoArticleASObject AudioAudioASObjectColeccin CollectionASObjectComentarioCommentASObjectEliminado el %1 Deleted on %1ASObjectArchivoFileASObject GrupoGroupASObject ImagenImageASObject4No hay ubicacin detalladaNo detailed locationASObjectNotaNoteASObjectOtroOtherASObject VdeoVideoASObjecty %1 ms and %1 othersASObjecty uno ms and one otherASObject CiudadHometownASPerson0&Autorizar la aplicacin&Authorize Application AccountDialog&Cancelar&Cancel AccountDialog&Guardar datos &Save Details AccountDialog&Desbloquear&Unlock AccountDialogAhora se iniciar un navegador web, donde podrs obtener el cdigo de verificacinAA web browser will start now, where you can get the verifier code AccountDialog4Configuracin de la cuentaAccount Configuration AccountDialogCuando pulses este botn, se abrir un navegador web, solicitando autorizacin para DianaraYAfter clicking this button, a web browser will open, requesting authorization for Dianara AccountDialogfDianara tiene autorizacin para acceder a tu cuenta)Dianara is authorized to access your data AccountDialogIntroduce o pega aqu el codigo de verificacin proporcionado por tu servidor PumpBEnter or paste the verifier code provided by your Pump server here AccountDialogPrimero, introduce tu identificador Webfinger, tu direccin pump.io.5First, enter your Webfinger ID, your pump.io address. AccountDialog>Obtener cdigo de &verificacinGet &Verifier Code AccountDialogSi el navegador web no se abre automticamente, copia esta direccin manualmenteEIf the browser doesn't open automatically, copy this address manually AccountDialogSi an no tienes una cuenta, puedes registrar una en %1. Este enlace te llevar a un servidor pblico aleatorio.sIf you don't have an account yet, you can sign up for one at %1. This link will take you to a random public server. AccountDialog,Si necesitas ayuda: %1If you need help: %1 AccountDialogSi tu perfil est en https://pump.ejemplo/tunombre, entonces tu direccin es tunombre@pump.ejemplo_If your profile is at https://pump.example/yourname, then your address is yourname@pump.example AccountDialog<Una vez que hayas autorizado a Dianara desde la interfaz web de tu servidor Pump, recibirs un cdigo llamado VERIFIER. Cpialo y pgalo en el campo de abajo.Once you have authorized Dianara from your Pump server web interface, you'll receive a code called VERIFIER. Copy it and paste it into the field below. AccountDialogzPulsa Desbloquear si quieres configurar una cuenta diferente.:Press Unlock if you wish to configure a different account. AccountDialog4Gua de usuario de Pump.ioPump.io User Guide AccountDialogHEl cdigo de verificacin est vacoVerifier code is empty AccountDialog.Cdigo de verificacin:Verifier code: AccountDialog<Tu direccin Pump no es vlidaYour Pump address is invalid AccountDialog*Tu direccin pump.io:Your Pump.io address: AccountDialogRTu cuenta est configurada correctamente.$Your account is properly configured. AccountDialogTu direccin es como usuario@servidorpump.org, y puedes encontrarla en tu perfil, en la interfaz web.kYour address looks like username@pumpserver.org, and you can find it in your profile, in the web interface. AccountDialogVTu direccin, como usuario@servidorpump.org*Your address, like username@pumpserver.org AccountDialog.&Aadir a seleccionados&Add to SelectedAudienceSelector&Cancelar&CancelAudienceSelector &Hecho&DoneAudienceSelectorLista 'Cc' 'Cc' ListAudienceSelectorLista 'Para' 'To' ListAudienceSelector&Todos los contactos All ContactsAudienceSelectorBorrar &lista Clear &ListAudienceSelector8Selecciona gente de la lista de la izquierda. Puedes arrastrarlos con el ratn, hacer clic o doble clic en ellos, o seleccionarlos y usar el botn de abajo.:ON THE LEFT should change to ON THE RIGHT in RTL languagesSelect people from the list on the left. You can drag them with the mouse, click or double-click on them, or select them and use the button below.AudienceSelector$Gente seleccionadaSelected PeopleAudienceSelector&No&No AvatarButton(&S, dejar de seguir&Yes, stop following AvatarButtondEsts seguro de que quieres dejar de seguir a %1?+Are you sure you want to stop following %1? AvatarButtonVer mensajesBrowse messages AvatarButton SeguirFollow AvatarButtonRAbrir el perfil de %1 en el navegador web Open %1's profile in web browser AvatarButtonFAbrir tu perfil en el navegador web Open your profile in web browser AvatarButton&Enviar mensaje a %1Send message to %1 AvatarButtonDejar de seguirStop following AvatarButton"Dejar de seguir?Stop following? AvatarButtonCambiarChange ColorPickerElige un colorChoose a color ColorPicker<A %1 les gusta este comentario%1 like this commentComment:A %1 le gusta este comentario%1 likes this commentComment&No&NoComment&S, eliminarlo&Yes, delete itCommentlEsts seguro de que quieres eliminar este comentario?-Are you sure you want to delete this comment?CommentEliminarDeleteComment EditarEditComment,Borrar este comentarioErase this commentCommentMe gustaLikeCommentnDecir que te gusta o que ya no te gusta este comentarioLike or unlike this commentComment Modificado el %1Modified on %1Comment2Modificar este comentarioModify this commentCommentPublicado el %1 Posted on %1Comment CitarQuoteCommentBResponder citando este comentarioReply quoting this commentCommentYa no me gustaUnlikeCommentDADVERTENCIA: Eliminar comentario?WARNING: Delete comment?CommentCancelarCancelCommenterBlock*Comprobar comentariosCheck for commentsCommenterBlockComentarCommentCommenterBlock2El comentario est vaco.Comment is empty.CommenterBlockHLos comentarios no estn disponiblesComments are not availableCommenterBlock&Editando comentarioEditing commentCommenterBlock8Error: Ya se est redactandoError: Already composingCommenterBlockvHa fallado la publicacin del comentario. Prueba de nuevo.#Posting comment failed. Try again.CommenterBlockjPulsa ESC para cancelar el comentario si no hay texto3Press ESC to cancel the comment if there is no textCommenterBlock,Actualizar comentariosReload commentsCommenterBlock,Enviando comentario...Sending comment...CommenterBlock4Mostrar los %1 comentariosShow all %1 commentsCommenterBlock4Actualizando comentario...Updating comment...CommenterBlockPuedes pulsar Control+Enter para enviar el comentario con el tecladoAYou can press Control+Enter to send the comment with the keyboardCommenterBlockNo puedes editar un comentario en este momento, porque ya se est redactando otro comentario.YYou can't edit a comment at this time, because another comment is already being composed.CommenterBlock&Formato&FormatComposer&No&NoComposer&S, cancelarlo&Yes, cancel itComposerTSeguro que quieres cancelar este mensaje?-Are you sure you want to cancel this message?ComposerNegritaBoldComposer*Cancelar el mensaje?Cancel message?ComposerrHaz clic aqu o pulsa Control+N para publicar una nota.../Click here or press Control+N to post a note...Composer(Error: URL no vlidaError: Invalid URLComposerFormato FormattingComposer TtuloHeaderComposer4Cuntas columnas (ancho)?How many columns (width)?Composer0Cuntas filas (altura)?How many rows (height)?Composer$Insertar un enlace Insert a linkComposerBInsertar una imagen desde una URLInsert an image from a URLComposerLInsertar una imagen desde un sitio webInsert an image from a web siteComposer,Insertar como imagen?Insert as image?Composer(Insertar como enlaceInsert as linkComposer8Insertar como imagen visibleInsert as visible imageComposerInsertar lnea Insert lineComposerCursivaItalicComposer ListaListComposerHacer un enlace Make a linkComposerNHacer un link con el texto seleccionadoMake a link from selected textComposer NormalNormalComposer.Pegar texto sin formatoPaste Text Without FormattingComposer(Bloque preformateadoPreformatted blockComposerBloque de cita Quote blockComposerTachado StrikethroughComposerSmbolosSymbolsComposer TablaTableComposer$Tamao de la tabla Table SizeComposer8Opciones de formato de textoText Formatting OptionsComposerLa direccin que has introducido (%1) no es vlida. Las direcciones de imagenes han de empezar por http:// o https://`The address you entered (%1) is not valid. Image addresses should begin with http:// or https://ComposervParece que el enlace que ests pegando apunta a una imagen.4The link you are pasting seems to point to an image.Composer4Escribe un comentario aquType a comment hereComposerNEscribe un mensaje aqu para publicarloType a message here to post itComposerEscribe o pega una direccin web aqu. El texto seleccionado (%1) ser convertido en un enlace.UType or paste a web address here. The selected text (%1) will be converted to a link.ComposerEscribe o pega una direccin web aqu. Tambin puedes seleccionar texto antes, para convertirlo en un enlace.`Type or paste a web address here. You could also select some text first, to turn it into a link.ComposerEscribe o pega la direccin de la imagen aqu. El enlace ha de apuntar al archivo de imagen directamente.UType or paste the image address here. The link must point to the image file directly.ComposerSubrayado UnderlineComposer&Cancelar&Cancel ConfigDialog$Pes&taas movibles &Movable tabs ConfigDialog\&Mensajes por pgina, lnea temporal principal&Posts per page, main timeline ConfigDialog,&Guardar configuracin&Save Configuration ConfigDialog2Posicin de las &pestaas&Tabs position ConfigDialog$Todos los archivos All files ConfigDialogSiempreAlways ConfigDialog:Cualquier actividad destacadaAny highlighted activity ConfigDialog>Como notificaciones del sistemaAs system notifications ConfigDialog,Tamao de los avatares Avatar size ConfigDialogParte inferiorBottom ConfigDialogColoresColors ConfigDialogComentariosComments ConfigDialog EditorComposer ConfigDialog(&Icono personalizado Custom &Icon ConfigDialog&Icono personalizado Custom icon ConfigDialogPredeterminadoDefault ConfigDialogJDianara guarda datos en esta carpeta:#Dianara stores data in this folder: ConfigDialog`No informar a los seguidores al seguir a alguien-Don't inform followers when following someone ConfigDialog`No informar a los seguidores al manipular listas*Don't inform followers when handling lists ConfigDialog2No mostrar notificacionesDon't show notifications ConfigDialog$Normas de filtradoFiltering rules ConfigDialogTipos de letraFonts ConfigDialog$Opciones generalesGeneral Options ConfigDialog6Ocultar mensajes duplicadosHide duplicated posts ConfigDialog4Ocultar ventana al iniciarHide window on startup ConfigDialogTDestacar comentarios del autor del mensaje Highlight post author's comments ConfigDialog@Destacar tus propios comentariosHighlight your own comments ConfigDialogbActividades destacadas en la lnea temporal menor$Highlighted activities in minor feed ConfigDialogPActividades destacadas, excepto las mas#Highlighted activities, except mine ConfigDialog&Mensajes destacadosHighlighted posts ConfigDialogLIgnorar errores de SSL en las imgenesIgnore SSL errors in images ConfigDialog$Archivos de imagen Image files ConfigDialogPInformar slo al autor de los "Me gusta")Inform only the author when liking things ConfigDialog Imagen no vlida Invalid image ConfigDialogTElemento destacado por normas de filtrado.(Item highlighted due to filtering rules. ConfigDialog*El elemento es nuevo. Item is new. ConfigDialogdSaltar a la lnea de nuevos mensajes al actualizar Jump to new posts line on update ConfigDialogLado izquierdo(tabs on left side/west; RTL not affected Left side ConfigDialog2Lneas temporales menores Minor Feeds ConfigDialog(Configuracin de redNetwork configuration ConfigDialog NuncaNever ConfigDialogZNuevas actividades en la lnea temporal menorNew activities in minor feed ConfigDialogMensajes nuevos New posts ConfigDialog8Estilo de las notificacionesNotification Style ConfigDialogNotificaciones Notifications ConfigDialog8Notificar cuando se reciban:Notify when receiving: ConfigDialog^Slo para imgenes insertadas desde sitios web.(Only for images inserted from web sites. ConfigDialogJSe usarn las notificaciones propias.Own notifications will be used. ConfigDialog2Contenido de los mensajes Post Contents ConfigDialog.Ttulos de los mensajes Post Titles ConfigDialogMensajesPosts ConfigDialogZMensajes por pgina, &otras lneas temporales Posts per page, &other timelines ConfigDialogPrivacidadPrivacy ConfigDialog"Ajustes de pro&xyPro&xy Settings ConfigDialog4Configuracin del programaProgram Configuration ConfigDialogPMensajes pblicos de manera &predefinidaPublic posts as &default ConfigDialogLado derecho)tabs on right side/east; RTL not affected Right side ConfigDialogS&eleccionar... S&elect... ConfigDialog<Selecciona icono personalizadoSelect custom icon ConfigDialog&Configurar f&iltrosSet Up F&ilters ConfigDialog<Mostrar contador de caracteresShow character counter ConfigDialog`Mostrar informacin adicional en los compartidosShow extended share information ConfigDialog2Mostrar informacin extraShow extra information ConfigDialog\Mostrar informacin de los mensajes eliminados"Show information for deleted posts ConfigDialogfMostrar fragmentos en las lneas temporales menoresShow snippets in minor feeds ConfigDialog0Mostrar tu avatar actualShow your current avatar ConfigDialog0Lmite de los fragmentos Snippet limit ConfigDialog&Bandeja del sistema System Tray ConfigDialogP&Tipo de icono en la bandeja del sistemaSystem Tray Icon &Type ConfigDialogJIcono del sistema, si est disponibleSystem iconset, if available ConfigDialogjLas notificaciones del sistema no estn disponibles!'System notifications are not available! ConfigDialogLa actividad es en respuesta a alguna cosa hecha por ti, como un comentario publicado en respuesta a una de tus notas.jThe activity is in reply to something done by you, such as a comment posted in reply to one of your notes. ConfigDialogLa actividad est relacionada con uno de tus objetos, como por ejemplo cuando a alguien le gusta uno de tus mensajes.YThe activity is related to one of your objects, such as someone liking one of your posts. ConfigDialogHLa imagen seleccionada no es vlida. The selected image is not valid. ConfigDialog>Esto es una notificacin bsicaThis is a basic notification ConfigDialogHEsto es una notificacin del sistemaThis is a system notification ConfigDialog`Intervalo de &actualizacin de la lnea temporalTimeline &update interval ConfigDialog"Lneas temporales Timelines ConfigDialogParte superiorTop ConfigDialognUsar nombre del adjunto como ttulo inicial del mensaje-Use attachment filename as initial post title ConfigDialog"Usar con cuidado.Use with care. ConfigDialogUsado tambin cuando se destacan mensajes dirigidos a ti en las lneas temporales.DUsed also when highlighting posts addressed to you in the timelines. ConfigDialogUsado tambin cuando se destacan tus propios mensajes en las lneas temporales.