pumpa-0.9.2/0000755000175000017500000000000012653206625011345 5ustar matsmatspumpa-0.9.2/images/0000755000175000017500000000000012653206625012612 5ustar matsmatspumpa-0.9.2/images/glyphicons_200_download.png0000644000175000017500000000264412653206625017755 0ustar matsmatsPNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ?ܞCIIDATxV 0 ]J`e@(BA18A(A=yRڮ];KOZۯ&.s$@U/%T iMD ྀ J.In}HH8=OjD*#A=[FNR$r)P:BDݥ0Nz2 81 D$khwK V~Pt!*t"SE-43 Sp1|l),iȲ.)mdq#w QW;޲@g~F_A5KfhnqLl}rumu`. IENDB`pumpa-0.9.2/images/glyphicons_197_remove.png0000644000175000017500000000260512653206625017457 0ustar matsmatsPNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons u=*IDATxV }``z>N)=~ ;' XDlRHV%M?RJGe| Հ0VG*=Ill?D@㪴GЇ rIH트'`[#,d\;/ͬJW^*#6Ė!]EԦ$s6yELB4RNRYV]eHMxYRobx2ő4bv #qYч$ckڡt2gNn3mo+3Ć5!}qrsKI= _ i0yIENDB`pumpa-0.9.2/images/pumpa.png0000644000175000017500000000124612653206625014445 0ustar matsmatsPNG  IHDR szzbKGD pHYsodtIME!5,3IDATXVKA}s9pa.qE]X )WbkK0m:M<,z`c.Y Ass7;;Q({vnfq` c  x0MӯXy:2-7=E|]NEsK&+=7"; NfP(䒗d§)U Xj^Uvkt]pd,FsW6kLK7YJM} h&V Ron١#6aLl Sz8PE*9Afy0*X7^]|O>~/ [N``ܻLwF@;2u~imo7{@%b!k+&N:q<zIENDB`pumpa-0.9.2/images/pumpa_glow.png0000644000175000017500000000202612653206625015472 0ustar matsmatsPNG  IHDR szzgAMA abKGD pHYsodtIME$IDATXkG?n0$9I4Jc @("ziEB{M=۞ 4CP۸9Uލky=+ɖ,%97};‰_͛E넞7.z|s׉/~}Kt/G"p^Dn~} { 2)rYLp `/ˆ y]=:HcSMCf{smy)܌WyC7;7pwhx3kX6']IQl] wNM"ݝ8BsoLpgg(+5s>!ߚm c~0}Ⱦ"j5g t+>芁%[KutZƸ0%r1kk5LE 1KuoЭ~Lͧ1Whn`[97MͧW$_ V~ֱ^(:Rt#w P$~'LE-!yUīek'Zp1T')EЖ0-?!)E0dva=S`S2wQiMD˒Rğy4 } D',ھR5ZdR8 )>|Ch0S6c9]"k)NN6'Ny,1 9:`:. x_b vl ߣ(XK>ԁ"lF†FL~lu^ɽary5d,=ȏ;-;F=Y3c> %{ FɌ!EļH&;!E=N+S}W̷7*wʖLْɏ;=P1>8৺t(O|%#bE>?d^SGx֭~_L7艝MpIENDB`pumpa-0.9.2/images/glyphicons_199_ban.png0000644000175000017500000000261012653206625016720 0ustar matsmatsPNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons O-IDATxĖ d#t7:JG8#0nab rk .cr&7{ d[C|(l 7CiMZ͐iE26*8JJ 6}1Q-0]6wMl}qsKl| 0rEMIENDB`pumpa-0.9.2/images/loader.gif0000644000175000017500000000347112653206625014554 0ustar matsmatsGIF89aFFFzzzXXX$$$666hhh! NETSCAPE2.0!Created with ajaxload.info! ,w  !DBAH¬aD@ ^AXP@"UQ# B\; 1 o:2$v@ $|,3 _# d53" s5 e!! ,v i@e9DAA/`ph$Ca%@ pHxFuSx# .݄YfL_" p 3BW ]|L \6{|z87[7!! ,x  e9DE"2r,qPj`8@8bH, *0- mFW9LPE3+ (B"  f{*BW_/ @_$~Kr7Ar7!! ,v 4e9!H"* Q/@-4ép4R+-pȧ`P(6᠝U/  *,)(+/]"lO/*Ak K]A~666!! ,l ie9"* -80H=N; TEqe UoK2_WZ݌V1jgWe@tuH//w`?f~#6#!! ,~ ,e9"* ; pR%#0` 'c(J@@/1i4`VBV u}"caNi/ ] ))-Lel  mi} me[+!! ,y Ie9"M6*¨"7E͖@G((L&pqj@Z %@wZ) pl( ԭqu*R&c `))( s_J>_\'Gm7$+!! ,w Ie9*, (*(B5[1 ZIah!GexzJ0e6@V|U4Dm%$͛p \Gx }@+| =+ 1- Ea5l)+!! ,y )䨞'AKڍ,E\(l&;5 5D03a0--ÃpH4V % i p[R"| #  6iZwcw*!! ,y )䨞,K*0 a;׋аY8b`4n ¨Bbbx,( Ƚ  % >  2*i* /:+$v*!! ,u )䨞l[$ Jq[q 3`Q[5:IX!0rAD8 CvHPfiiQAP@pC %D PQ46  iciNj0w )#!! ,y ). q ,G Jr(J8 C*B,&< h W~-`, ,>; 8RN<, <1T] c' qk$ @)#!;pumpa-0.9.2/images/empty.gif0000644000175000017500000000011412653206625014433 0ustar matsmatsGIF89a!Created with GIMP! ,ڋ>;pumpa-0.9.2/images/default.png0000644000175000017500000000233612653206625014750 0ustar matsmatsPNG  IHDR``w8sBIT|d pHYs  ~tEXtCreation Time6/20/08"tEXtSoftwareAdobe Fireworks CS4Ӡ7IDATx-S0_+"VV8O]Yܙ5E uTVfq\-lONRh&9I.+"l\r?O' `& `& `& `& `& `& `& `Lxyy0 'x<XVR"2!=Wu){4M*GE1ӓ͋w1,p{{ )型h}CkMZR EQx34 UUZL-)nL HI,f R/`dYFY3&`X@$(0U 8z4D= 9RR?zM^sA&T,< R YdR܀rΏ&^>gC!p08\!)!,e1;z&Z^"a#a C}8!16a~ݎ꿨ha?@k뼽O(MS\__;"}m~駐Rb^'$dƤ(  Àᰈ/V. pumpa-0.9.2/docs/0000755000175000017500000000000012653206625012275 5ustar matsmatspumpa-0.9.2/docs/pumpa.10000644000175000017500000000255512653206625013510 0ustar matsmats.\" Hey, EMACS: -*- nroff -*- .\" (C) Copyright 2013 Mats Sjöberg , .\" .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH PUMPA 1 "December 16, 2013" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME pumpa \- Pumpa is a simple desktop client for pump.io. .SH SYNOPSIS .B pumpa .OP \-c alternative.conf .OP \-l locale .br .B pumpa -h .SH DESCRIPTION Pumpa is a simple Qt desktop client for identi.ca and other pump.io-based federated social network services. .SH OPTIONS A summary of options is included below. .TP \-h, \-\-help Show summary of options. .IP "\-c \fIalternative.conf\fR" Use \fIalternative.conf\fR instead of the standard configuration file. .TP .IP "\-l \fIlocale\fR" Set locale to \fIlocale\fR (e.g. es, en_GB) instead of the auto-detected locale. .SH AUTHOR Pumpa was written by Mats Sjöberg pumpa-0.9.2/LICENSE0000644000175000017500000010451312653206625012356 0ustar matsmats GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 3 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . pumpa-0.9.2/src/0000755000175000017500000000000012653206625012134 5ustar matsmatspumpa-0.9.2/src/fullobjectwidget.cpp0000644000175000017500000006651612653206625016213 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "fullobjectwidget.h" #include "pumpa_defines.h" #include "util.h" #include "shortobjectwidget.h" #include "pumpasettings.h" #include #include #include //------------------------------------------------------------------------------ QSet s_allowedTags; //------------------------------------------------------------------------------ FullObjectWidget::FullObjectWidget(QASObject* obj, QWidget* parent, bool childWidget) : ObjectWidgetWithSignals(parent), m_infoLabel(NULL), m_likesLabel(NULL), m_sharesLabel(NULL), m_titleLabel(NULL), m_hasMoreButton(NULL), m_favourButton(NULL), m_shareButton(NULL), m_commentButton(NULL), m_followButton(NULL), m_deleteButton(NULL), m_editButton(NULL), m_object(NULL), m_actor(NULL), m_author(NULL), m_activity(NULL), m_childWidget(childWidget) { #ifdef DEBUG_WIDGETS qDebug() << "Creating FullObjectWidget"; #endif QVBoxLayout* rightLayout = new QVBoxLayout; rightLayout->setContentsMargins(0, 0, 0, 0); m_contentLayout = new QVBoxLayout; m_contentLayout->setContentsMargins(0, 0, 0, 0); m_titleLabel = new RichTextLabel(this); connect(m_titleLabel, SIGNAL(linkHovered(const QString&)), this, SIGNAL(linkHovered(const QString&))); m_contentLayout->addWidget(m_titleLabel); m_imageLabel = new ImageLabel(this); connect(m_imageLabel, SIGNAL(clicked()), this, SLOT(imageClicked())); m_imageLabel->setCursor(Qt::PointingHandCursor); m_contentLayout->addWidget(m_imageLabel); m_streamLabel = new RichTextLabel(this); connect(m_streamLabel, SIGNAL(linkHovered(const QString&)), this, SIGNAL(linkHovered(const QString&))); m_contentLayout->addWidget(m_streamLabel, 0, Qt::AlignTop); m_textLabel = new RichTextLabel(this); connect(m_textLabel, SIGNAL(linkHovered(const QString&)), this, SIGNAL(linkHovered(const QString&))); m_contentLayout->addWidget(m_textLabel, 0, Qt::AlignTop); m_infoLabel = new RichTextLabel(this); connect(m_infoLabel, SIGNAL(linkHovered(const QString&)), this, SIGNAL(linkHovered(const QString&))); m_contentLayout->addWidget(m_infoLabel, 0, Qt::AlignTop); m_buttonLayout = new QHBoxLayout; m_favourButton = new TextToolButton(this); connect(m_favourButton, SIGNAL(clicked()), this, SLOT(favourite())); m_buttonLayout->addWidget(m_favourButton, 0, Qt::AlignTop); m_followAuthorButton = new TextToolButton(this); connect(m_followAuthorButton, SIGNAL(clicked()), this, SLOT(onFollowAuthor())); m_buttonLayout->addWidget(m_followAuthorButton, 0, Qt::AlignTop); m_shareButton = new TextToolButton(this); connect(m_shareButton, SIGNAL(clicked()), this, SLOT(onRepeatClicked())); m_buttonLayout->addWidget(m_shareButton, 0, Qt::AlignTop); m_deleteButton = new TextToolButton(tr("delete"), this); connect(m_deleteButton, SIGNAL(clicked()), this, SLOT(onDeleteClicked())); m_buttonLayout->addWidget(m_deleteButton, 0, Qt::AlignTop); m_editButton = new TextToolButton(tr("edit"), this); connect(m_editButton, SIGNAL(clicked()), this, SLOT(onEditClicked())); m_buttonLayout->addWidget(m_editButton, 0, Qt::AlignTop); m_commentButton = new TextToolButton(tr("comment", "Button to comment on a post"), this); connect(m_commentButton, SIGNAL(clicked()), this, SLOT(reply())); m_buttonLayout->addWidget(m_commentButton, 0, Qt::AlignTop); m_followButton = new TextToolButton(this); connect(m_followButton, SIGNAL(clicked()), this, SLOT(onFollow())); m_buttonLayout->addWidget(m_followButton, 0, Qt::AlignTop); m_buttonLayout->addStretch(); #ifdef DEBUG_BUTTONS m_loadRepliesButton = new TextToolButton("R", this); connect(m_loadRepliesButton, SIGNAL(clicked()), this, SLOT(onLoadRepliesClicked())); m_buttonLayout->addWidget(m_loadRepliesButton, 0, Qt::AlignTop); m_dumpJsonButton = new TextToolButton("J", this); connect(m_dumpJsonButton, SIGNAL(clicked()), this, SLOT(onDumpJsonClicked())); m_buttonLayout->addWidget(m_dumpJsonButton, 0, Qt::AlignTop); #endif m_commentsLayout = new QVBoxLayout; rightLayout->addLayout(m_contentLayout); rightLayout->addLayout(m_buttonLayout); rightLayout->addLayout(m_commentsLayout); // If this object is not an actor itself, show the author in the // avatar image. m_actorWidget = new ActorWidget(NULL, this); connect(m_actorWidget, SIGNAL(follow(QString, bool)), this, SIGNAL(follow(QString, bool))); connect(m_actorWidget, SIGNAL(lessClicked()), this, SIGNAL(lessClicked())); m_lessButton = new TextToolButton("-", this); connect(m_lessButton, SIGNAL(clicked()), this, SIGNAL(lessClicked())); QHBoxLayout* acrossLayout = new QHBoxLayout; acrossLayout->setSpacing(10); acrossLayout->addWidget(m_actorWidget, 0, Qt::AlignTop); acrossLayout->addLayout(rightLayout); acrossLayout->addWidget(m_lessButton, 0, Qt::AlignTop); changeObject(obj); setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); setLayout(acrossLayout); } //------------------------------------------------------------------------------ FullObjectWidget::~FullObjectWidget() { #ifdef DEBUG_WIDGETS qDebug() << "Deleting FullObjectWidget" << m_object->id(); #endif } //------------------------------------------------------------------------------ void FullObjectWidget::changeObject(QASAbstractObject* obj) { if (m_object != NULL) { disconnect(m_object, SIGNAL(changed()), this, SLOT(onChanged())); if (m_author) { disconnect(m_author, SIGNAL(changed()), m_actorWidget, SLOT(updateMenu())); disconnect(m_author, SIGNAL(changed()), this, SLOT(updateFollowAuthorButton())); disconnect(m_author, SIGNAL(changed()), this, SLOT(updateDeleteEditButtons())); } QASObjectList* ol = m_object->replies(); if (ol) disconnect(ol, SIGNAL(changed()), this, SLOT(onChanged())); clearObjectList(); } m_object = qobject_cast(obj); if (!m_object) return; const QString objType = m_object->type(); connect(m_object, SIGNAL(changed()), this, SLOT(onChanged())); if (m_childWidget || objType == "person") { setLineWidth(1); setFrameStyle(QFrame::StyledPanel | QFrame::Plain); m_lessButton->setVisible(false); } else { setLineWidth(0); setFrameStyle(QFrame::NoFrame | QFrame::Plain); m_lessButton->setVisible(true); } updateTitle(); if (objType == "image") { m_imageLabel->setVisible(true); m_imageUrl = m_object->imageUrl(); updateImage(); } else { m_imageLabel->setVisible(false); } if (!m_object->streamUrl().isEmpty()) { m_streamLabel->setVisible(true); m_streamLabel->setText(QString("Media: %2"). arg(m_object->streamUrl(), m_object->type())); } else if (objType == "file" && !m_object->fileUrl().isEmpty()) { m_streamLabel->setVisible(true); m_streamLabel->setText(QString("File: %2"). arg(m_object->fileUrl(), m_object->mimeType())); } else { m_streamLabel->setVisible(false); } m_author = m_object->author(); if (m_author) { connect(m_author, SIGNAL(changed()), m_actorWidget, SLOT(updateMenu())); connect(m_author, SIGNAL(changed()), this, SLOT(updateFollowAuthorButton())); connect(m_author, SIGNAL(changed()), this, SLOT(updateDeleteEditButtons())); } updateFollowAuthorButton(); updateDeleteEditButtons(); m_followButton->setVisible(objType == "person"); m_actor = m_object->asActor(); QASActor* actorOrAuthor = m_actor ? m_actor : m_author; m_actorWidget->setActor(actorOrAuthor); onChanged(); } //------------------------------------------------------------------------------ void FullObjectWidget::updateTitle() { if (!m_object->displayName().isEmpty()) { m_titleLabel->setText("" + m_object->displayName() + ""); m_titleLabel->setVisible(true); } else { m_titleLabel->setVisible(false); } } //------------------------------------------------------------------------------ bool FullObjectWidget::hasValidIrtObject() { QASObject* irtObj = m_object->inReplyTo(); return irtObj && !irtObj->id().isEmpty(); } //------------------------------------------------------------------------------ bool FullObjectWidget::isCommentable() const { return m_object && m_object->type() != "person" && !m_object->isDeleted(); } //------------------------------------------------------------------------------ void FullObjectWidget::onChanged() { if (!m_object) return; m_commentable = isCommentable(); m_favourButton->setVisible(m_commentable); m_shareButton->setVisible(m_commentable); m_commentButton->setVisible(m_commentable); if (m_object->isDeleted()) m_imageLabel->setVisible(false); updateLikes(); updateShares(); updateFavourButton(); updateShareButton(); m_commentButton->setVisible(m_commentable && (m_object->type() != "comment" || hasValidIrtObject())); updateFollowButton(); updateFollowAuthorButton(); updateDeleteEditButtons(); m_actorWidget->updateMenu(); QString text = m_object->content(); if (m_actor) { text = m_actor->summary(); if (text.isEmpty()) text = tr("[No description]"); } setText(processText(text, true)); updateInfoText(); updateTitle(); QASObjectList* ol = m_object->replies(); if (ol) { connect(ol, SIGNAL(changed()), this, SLOT(onChanged()), Qt::UniqueConnection); addObjectList(ol); } } //------------------------------------------------------------------------------ void FullObjectWidget::setText(QString text) { m_textLabel->setText(text); } //------------------------------------------------------------------------------ void FullObjectWidget::updateInfoText() { if (m_infoLabel == NULL) return; QString infoStr; if (m_actor) { QString aid = m_actor->webFinger(); infoStr = QString("%1").arg(aid).arg(m_object->url()); QString location = m_actor->location(); if (!location.isEmpty()) infoStr += " " + QString(tr("at %1", "location of person")).arg(location); if (m_actor->updatedDate().isValid()) infoStr += ".
" + QString(tr("Profile last updated %1")). arg(relativeFuzzyTime(m_actor->updatedDate(), true)); } else { infoStr = QString("%1"). arg(relativeFuzzyTime(m_object->published())). arg(m_object->url()); if (m_object->published() != m_object->updatedDate()) infoStr += ", " + QString(tr("updated %1")). arg(relativeFuzzyTime(m_object->updatedDate())); QASActor* author = m_object->author(); if (author) infoStr = QString("%1"). arg(author->displayName()). arg(author->url()) + " " + QString(tr("at %1", "time when post was published")).arg(infoStr); QASLocation* loc = m_object->location(); if (!loc->isEmpty()) { QString locInfo = loc->displayName(); if (loc->hasPosition()) locInfo = QString("%1").arg(locInfo). arg(loc->osmURL(10)); infoStr += " @ " + locInfo; } } m_infoLabel->setText(infoStr + "."); } //------------------------------------------------------------------------------ void FullObjectWidget::updateFavourButton(bool wait) { if (!m_favourButton) return; QString text = m_object->liked() ? tr("unlike") : tr("like"); if (wait) text = "..."; m_favourButton->setText(text); } //------------------------------------------------------------------------------ void FullObjectWidget::updateShareButton(bool /*wait*/) { if (!m_shareButton) return; m_shareButton->setText(tr("share")); } //------------------------------------------------------------------------------ bool FullObjectWidget::isFollowable(QASObject* obj) const { if (!obj) return false; QASActor* actor = obj->asActor(); return obj->type() == "person" && actor && !actor->isYou(); } //------------------------------------------------------------------------------ void FullObjectWidget::updateFollowButton(bool /*wait*/) { if (!m_followButton) return; if (!isFollowable(m_object) || !m_actor->followedKnown()) { m_followButton->setVisible(false); return; } m_followButton->setVisible(true); m_followButton->setText(m_actor->followed() ? tr("stop following") : tr("follow")); } //------------------------------------------------------------------------------ void FullObjectWidget::updateFollowAuthorButton(bool /*wait*/) { if (!m_followAuthorButton) return; if (!m_author || !isFollowable(m_author) || m_author->followed() || !m_author->followedKnown()) { m_followAuthorButton->setVisible(false); return; } m_followAuthorButton->setVisible(true); QString text = QString(tr("follow %1")).arg(m_author->preferredUsername()); m_followAuthorButton->setText(text); } //------------------------------------------------------------------------------ void FullObjectWidget::updateDeleteEditButtons() { bool isYours = m_author && m_author->isYou(); if (isCommentable()) { m_deleteButton->setVisible(isYours); m_editButton->setVisible(isYours); } else { m_deleteButton->setVisible(false); m_editButton->setVisible(false); } } //------------------------------------------------------------------------------ void FullObjectWidget::updateImage() { FileDownloadManager* fdm = FileDownloadManager::getManager(); if (fdm->hasFile(m_imageUrl)) { if (fdm->supportsAnimation(m_imageUrl)) { QMovie* mov = fdm->movie(m_imageUrl); m_imageLabel->setMovie(mov); mov->start(); } else { m_imageLabel->setPixmap(fdm->pixmap(m_imageUrl, ":/images/image_broken.png")); } } else { m_imageLabel->setPixmap(QPixmap(":/images/image_downloading.png")); if (!m_imageUrl.isEmpty()) { FileDownloader* fd = fdm->download(m_imageUrl); connect(fd, SIGNAL(fileReady()), this, SLOT(updateImage()), Qt::UniqueConnection); } } } //------------------------------------------------------------------------------ void FullObjectWidget::updateLikes() { size_t nl = m_object->numLikes(); if (nl <= 0) { if (m_likesLabel != NULL) { m_contentLayout->removeWidget(m_likesLabel); delete m_likesLabel; m_likesLabel = NULL; } return; } QASActorList* likes = m_object->likes(); QString text; if (m_likesLabel == NULL) { m_likesLabel = new RichTextLabel(this); connect(m_likesLabel, SIGNAL(linkHovered(const QString&)), this, SIGNAL(linkHovered(const QString&))); m_contentLayout->addWidget(m_likesLabel); } QString nstr = likes->actorNames(); if (likes->onlyYou()) text = QString(" ") + tr("You like this."); else if (nl==1) text = " " + QString(tr("%1 likes this.")).arg(nstr); else text = " " + QString(tr("%1 like this.")).arg(nstr); m_likesLabel->setText(text); } //------------------------------------------------------------------------------ void FullObjectWidget::updateShares() { size_t ns = m_object->numShares(); if (!ns) { if (m_sharesLabel != NULL) { m_contentLayout->removeWidget(m_sharesLabel); delete m_sharesLabel; m_sharesLabel = NULL; } return; } if (m_sharesLabel == NULL) { m_sharesLabel = new RichTextLabel(this); connect(m_sharesLabel, SIGNAL(linkHovered(const QString&)), this, SIGNAL(linkHovered(const QString&))); m_contentLayout->addWidget(m_sharesLabel); } QString text; if (m_object->shares()->size()) { text = m_object->shares()->actorNames(); int others = ns-m_object->shares()->size(); if (others >= 1) text += QString(" ") + tr("and %Ln other person(s)", 0, others); if (ns > 1) text += QString(" ") + tr("shared this.", "Many persons shared"); else text += QString(" ") + tr("shared this.", "One person shared"); } else { if (ns >= 1) text = tr("%Ln person(s) shared this.", 0, ns); } m_sharesLabel->setText(text); } //------------------------------------------------------------------------------ void FullObjectWidget::imageClicked() { QString url = m_object->fullImageUrl(); if (!url.isEmpty()) QDesktopServices::openUrl(url); } //------------------------------------------------------------------------------ void FullObjectWidget::addObjectList(QASObjectList* ol) { int li = 0; // index where to insert next widget in the layout int li_before = li; if (m_hasMoreButton != NULL) li++; /* For now we sort by time, or more accurately by whatever number the QASObject::sortInt() returns. Higher number is newer, goes further down the list. */ for (size_t j=0; jsize(); j++) { QASObject* replyObj = ol->at(ol->size()-j-1); QString replyId = replyObj->id(); qint64 sortInt = replyObj->sortInt(); // skip existing or deleted items if (m_repliesMap.contains(replyId) || replyObj->isDeleted()) continue; // Find position (this could be done faster, but probably not // worth the trouble, since there won't be thousands of replies // :-) int i = 0; // index into m_repliesList while (i < m_repliesList.size() && m_repliesList[i]->id() != replyId && m_repliesList[i]->sortInt() < sortInt) i++; if (i < m_repliesList.size() && m_repliesList[i]->id() == replyId) { qDebug() << "ERROR: addObjectList: item not in replies map!!"; continue; } FullObjectWidget* ow = new FullObjectWidget(replyObj, this, true); ObjectWidgetWithSignals::connectSignals(ow, this); m_commentsLayout->insertWidget(li + i, ow); m_repliesList.insert(i, replyObj); m_repliesMap.insert(replyId); } PumpaSettings* settings = PumpaSettings::getSettings(); if (ol->hasMore() && ((qulonglong)m_repliesList.size() < ol->totalItems()) && (settings && m_object->replies()->apiLink().startsWith(settings->siteUrl()))) { addHasMoreButton(ol, li_before); } else if (m_hasMoreButton != NULL) { m_commentsLayout->removeWidget(m_hasMoreButton); delete m_hasMoreButton; m_hasMoreButton = NULL; } } //------------------------------------------------------------------------------ void FullObjectWidget::addHasMoreButton(QASObjectList*, int li) { QString buttonText = tr("Show all replies"); if (m_hasMoreButton == NULL) { m_hasMoreButton = new QPushButton(this); m_hasMoreButton->setFocusPolicy(Qt::NoFocus); m_commentsLayout->insertWidget(li, m_hasMoreButton); connect(m_hasMoreButton, SIGNAL(clicked()), this, SLOT(onHasMoreClicked())); } m_hasMoreButton->setText(buttonText); } //------------------------------------------------------------------------------ void FullObjectWidget::onHasMoreClicked() { m_hasMoreButton->setText("..."); refreshObject(m_object->replies()); } //------------------------------------------------------------------------------ void FullObjectWidget::favourite() { updateFavourButton(true); emit like(m_object); } //------------------------------------------------------------------------------ void FullObjectWidget::reply() { QASActivity* act = m_activity; if (act == NULL) act = m_object->postingActivity(); if (act == NULL && hasValidIrtObject()) act = m_object->inReplyTo()->postingActivity(); QASObjectList* to = act && act->hasTo() ? act->to() : NULL; QASObjectList* cc = act && act->hasCc() ? act->cc() : NULL; emit newReply(m_object, to, cc); } //------------------------------------------------------------------------------ QString FullObjectWidget::typeName() const { QString typeName = tr("post", "Name of object type."); QString tn = m_object->type(); if (tn == "note") typeName = tr("note", "Name of object type."); else if (tn == "comment") typeName = tr("comment", "Name of object type."); else if (tn == "image") typeName = tr("image", "Name of object type."); return typeName; } //------------------------------------------------------------------------------ QString FullObjectWidget::textExcerpt() const { const int max_len = 40; QString excerpt = m_object->excerpt(). replace(QRegExp("\\s+"), " "); if (excerpt.count() > max_len) { excerpt.truncate(max_len-4); excerpt += " ..."; } return excerpt.trimmed(); } //------------------------------------------------------------------------------ void FullObjectWidget::onDeleteClicked() { QString msg = QString(tr("Are you sure you want to delete this %1?")). arg(typeName()) + "\n\"" + textExcerpt() + "\""; int ret = QMessageBox::warning(this, CLIENT_FANCY_NAME, msg, QMessageBox::Cancel | QMessageBox::Yes, QMessageBox::Cancel); if (ret == QMessageBox::Yes) emit deleteObject(m_object); } //------------------------------------------------------------------------------ void FullObjectWidget::onEditClicked() { if (m_author && m_author->isYou()) emit editObject(m_object); } //------------------------------------------------------------------------------ void FullObjectWidget::onRepeatClicked() { QString msg = QString(tr("Share this %1 by %2?")). arg(typeName()).arg(m_author->displayNameOrWebFinger()) + "\n\"" + textExcerpt() + "\""; int ret = QMessageBox::question(this, CLIENT_FANCY_NAME, msg, QMessageBox::Cancel | QMessageBox::Yes, QMessageBox::Yes); if (ret == QMessageBox::Yes) { updateShareButton(true); emit share(m_object); } } //------------------------------------------------------------------------------ void FullObjectWidget::onFollow() { updateFollowButton(true); updateFollowAuthorButton(true); if (isFollowable(m_actor)) emit follow(m_actor->id(), !m_actor->followed()); } //------------------------------------------------------------------------------ void FullObjectWidget::onFollowAuthor() { bool doFollow = !m_author->followed(); if (!doFollow) return; updateFollowButton(true); updateFollowAuthorButton(true); if (isFollowable(m_author)) emit follow(m_author->id(), doFollow); } //------------------------------------------------------------------------------ #ifdef DEBUG_BUTTONS void FullObjectWidget::onLoadRepliesClicked() { if (m_object) refreshObject(m_object->replies()); } //------------------------------------------------------------------------------ void FullObjectWidget::onDumpJsonClicked() { if (m_object) qDebug() << debugDumpJson(m_object->json(), "JSON"); } #endif //------------------------------------------------------------------------------ QString FullObjectWidget::processText(QString old_text, bool getImages) { if (s_allowedTags.isEmpty()) { s_allowedTags << "br" << "p" << "b" << "i" << "blockquote" << "div" << "abbr" << "code" << "h1" << "h2" << "h3" << "h4" << "h5" << "em" << "ol" << "li" << "ul" << "hr" << "strong" << "u"; s_allowedTags << "pre" << "s"; s_allowedTags << "a"; s_allowedTags << "img"; s_allowedTags << "table" << "tr" << "td" << "th" << "thead" << "tbody"; } QString text = old_text.trimmed(); int pos; // Shorten links that are too long, this is OK, since you can still // click the link. QRegExp rxa("]*href=([^>]+)[^>]*>([^<]*)"); pos = 0; while ((pos = rxa.indexIn(text, pos)) != -1) { int len = rxa.matchedLength(); QString url = rxa.cap(1).trimmed(); QString linkText = rxa.cap(2); if ((linkText.startsWith("http://") || linkText.startsWith("https://")) && linkText.length() > MAX_WORD_LENGTH) { linkText = linkText.left(MAX_WORD_LENGTH-3) + "..."; QString newText = QString("%2").arg(url).arg(linkText); text.replace(pos, len, newText); pos += newText.length(); } else pos += len; } // Detect single HTML tags for filtering. QRegExp rx("<(\\/?)([a-zA-Z0-9]+)([^>]*)>"); pos = 0; while ((pos = rx.indexIn(text, pos)) != -1) { int len = rx.matchedLength(); QString slash = rx.cap(1); QString tag = rx.cap(2).toLower(); QString inside = rx.cap(3); if (tag == "img") { // Replace img's with placeholder QString imagePlaceholder = "[image]"; if (getImages) { QRegExp rxi("\\s+src=\"?(" URL_REGEX ")\"?"); int spos = rxi.indexIn(inside); if (spos != -1) { QString imgSrc = rxi.cap(1); // qDebug() << "[DEBUG] processText: img" << imgSrc; FileDownloadManager* fdm = FileDownloadManager::getManager(); // only way in Qt to decode html entities in URL QTextDocument doc; doc.setHtml(imgSrc); imgSrc = doc.toPlainText(); if (fdm->hasFile(imgSrc)) { imagePlaceholder = QString(""). arg(fdm->fileName(imgSrc)).arg(imgSrc); } else if (!imgSrc.isEmpty()) { FileDownloader* fd = fdm->download(imgSrc); connect(fd, SIGNAL(fileReady()), this, SLOT(onChanged()), Qt::UniqueConnection); } } } text.replace(pos, len, imagePlaceholder); pos += imagePlaceholder.length(); // qDebug() << "[DEBUG] processText: removing image"; } else if (s_allowedTags.contains(tag)) { pos += len; } else { // drop all other HTML tags if (tag != "span") qDebug() << "[DEBUG] processText: dropping unsupported tag" << tag; text.remove(pos, len); } } text.replace("< ", "< "); // remove trailing
:s while (text.endsWith("
")) text.chop(4); return text; } //------------------------------------------------------------------------------ void FullObjectWidget::clearObjectList() { QLayoutItem* item; while ((item = m_commentsLayout->takeAt(0)) != 0) { if (dynamic_cast(item)) { QWidget* w = item->widget(); FullObjectWidget* ow = qobject_cast(w); if (ow) ObjectWidgetWithSignals::disconnectSignals(ow, this); delete w; } delete item; } m_repliesMap.clear(); m_repliesList.clear(); m_hasMoreButton = NULL; } //------------------------------------------------------------------------------ void FullObjectWidget::refreshTimeLabels() { updateInfoText(); for (int i=0; icount(); i++) { QLayoutItem* item = m_commentsLayout->itemAt(i); if (dynamic_cast(item)) { ObjectWidgetWithSignals* ow = qobject_cast(item->widget()); if (ow) ow->refreshTimeLabels(); } } } //------------------------------------------------------------------------------ void FullObjectWidget::disableLessButton() { m_lessButton->setVisible(false); } pumpa-0.9.2/src/oauthwizard.cpp0000644000175000017500000002362012653206625015204 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "pumpa_defines.h" #include "oauthwizard.h" #include "util.h" #include "json.h" #define EXAMPLE_ACCOUNT_ID "username@example.com" //------------------------------------------------------------------------------ OAuthFirstPage::OAuthFirstPage(QWidget* parent) : QWizardPage(parent) { setTitle(tr("Welcome to Pumpa!")); QVBoxLayout* layout = new QVBoxLayout(this); QLabel* infoLabel = new QLabel(tr("

In order to use pump.io you need to first register an " "account with a pump.io server. If you haven't done this yet " "you can do it now by trying out one of the existing public " "servers:
" "http://pump.io/tryit.html.

" "

When you are done enter your new pump.io account id " "below in the form of username@servername.

"), this); infoLabel->setOpenExternalLinks(true); infoLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); infoLabel->setWordWrap(true); layout->addWidget(infoLabel); layout->addStretch(); m_messageLabel = new QLabel(this); layout->addWidget(m_messageLabel); QLabel* accountIdLabel = new QLabel(tr("Your pump.io account id:"), this); QLineEdit* accountIdEdit = new QLineEdit(EXAMPLE_ACCOUNT_ID, this); accountIdLabel->setBuddy(accountIdEdit); connect(accountIdEdit, SIGNAL(textEdited(const QString&)), this, SIGNAL(completeChanged())); QCheckBox* sslCheckBox = new QCheckBox(tr("Use secure connection (recommended)"), this); sslCheckBox->setChecked(true); layout->addWidget(accountIdLabel); layout->addWidget(accountIdEdit); layout->addWidget(sslCheckBox); registerField("accountId*", accountIdEdit); registerField("useSsl*", sslCheckBox); setButtonText(QWizard::CommitButton, tr("Next")); setCommitPage(true); setLayout(layout); } //------------------------------------------------------------------------------ void OAuthFirstPage::setMessage(QString msg) { m_messageLabel->setText(msg); } //------------------------------------------------------------------------------ bool OAuthFirstPage::splitAccountId(QString& username, QString& server) const { QString accountId = field("accountId").toString().trimmed(); if (accountId == EXAMPLE_ACCOUNT_ID) return false; return splitWebfingerId(accountId, username, server); } //------------------------------------------------------------------------------ bool OAuthFirstPage::isComplete() const { QString username, server; return splitAccountId(username, server); } //------------------------------------------------------------------------------ bool OAuthFirstPage::validatePage() { QString username, server; bool ok = splitAccountId(username, server); if (!ok) return false; emit committed(username, server); return true; } //------------------------------------------------------------------------------ OAuthSecondPage::OAuthSecondPage(QWidget* parent) : QWizardPage(parent) { setTitle(tr("Authorise Pumpa")); QVBoxLayout* layout = new QVBoxLayout(this); QLabel* infoLabel = new QLabel(tr("In order for Pumpa to be able to read and post new messages " "to your pump.io account you need to grant Pumpa access via " "the web page. Pumpa will open the web page for you - just " "follow the instructions and copy & paste the " "verifier text string back into the field below. (The " "token should be automatically pre-filled.)"), this); infoLabel->setWordWrap(true); layout->addWidget(infoLabel); QLabel* tokenLabel = new QLabel(tr("Token:"), this); QLineEdit* tokenEdit = new QLineEdit(this); tokenLabel->setBuddy(tokenEdit); layout->addWidget(tokenLabel); layout->addWidget(tokenEdit); QLabel* verifierLabel = new QLabel(tr("Verifier:"), this); QLineEdit* verifierEdit = new QLineEdit(this); verifierLabel->setBuddy(verifierEdit); layout->addWidget(verifierLabel); layout->addWidget(verifierEdit); registerField("token*", tokenEdit); registerField("verifier*", verifierEdit); setLayout(layout); } //------------------------------------------------------------------------------ bool OAuthSecondPage::validatePage() { QString token = field("token").toString(); QString verifier = field("verifier").toString(); if (token.isEmpty() || verifier.isEmpty()) return false; emit committed(token, verifier); return true; } //------------------------------------------------------------------------------ OAuthWizard::OAuthWizard(QNetworkAccessManager* nam, KQOAuthManager* oam, QWidget* parent) : QWizard(parent), m_oam(oam), m_nam(nam) { setWindowTitle(CLIENT_FANCY_NAME); m_oar = new KQOAuthRequest(this); p1 = new OAuthFirstPage(this); p2 = new OAuthSecondPage(this); connect(p1, SIGNAL(committed(QString, QString)), this, SLOT(onFirstPageCommitted(QString, QString))); connect(p2, SIGNAL(committed(QString, QString)), this, SLOT(onSecondPageCommitted(QString, QString))); addPage(p1); addPage(p2); } //------------------------------------------------------------------------------ void OAuthWizard::notifyMessage(QString msg) { qDebug() << "[OAuthWizard]" << msg; } //------------------------------------------------------------------------------ void OAuthWizard::errorMessage(QString msg) { p1->setMessage(""+msg+""); qDebug() << "[OAuthWizard ERROR]" << msg; back(); } //------------------------------------------------------------------------------ void OAuthWizard::onFirstPageCommitted(QString username, QString server) { m_username = username; m_server = siteUrlFixer(server, field("useSsl").toBool()); m_clientRegTryCount = 0; registerOAuthClient(); } //------------------------------------------------------------------------------ void OAuthWizard::registerOAuthClient() { notifyMessage(tr("Registering client ...")); m_clientRegTryCount++; QUrl serverUrl(m_server); serverUrl.setPath("/api/client/register"); qDebug() << serverUrl; QNetworkRequest req; req.setUrl(serverUrl); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QVariantMap post; post["type"] = "client_associate"; post["application_type"] = "native"; post["application_name"] = CLIENT_FANCY_NAME; post["logo_uri"] = "http://saz.im/images/pumpa.png"; QByteArray postData = serializeJson(post); // qDebug() << "data=" << postData; QNetworkReply *reply = m_nam->post(req, postData); connect(reply, SIGNAL(finished()), this, SLOT(onOAuthClientRegDone())); } //------------------------------------------------------------------------------ void OAuthWizard::onOAuthClientRegDone() { QNetworkReply *reply = qobject_cast(sender()); if (reply->error() != QNetworkReply::NoError) { errorMessage(tr("Network error: ") + reply->errorString()); return; } QByteArray data = reply->readAll(); QVariantMap json = parseJson(data); m_clientId = json["client_id"].toString(); m_clientSecret = json["client_secret"].toString(); emit clientRegistered(m_username, m_server, m_clientId, m_clientSecret); notifyMessage(QString(tr("Registered client to [%1] successfully.")). arg(m_server)); getOAuthAccess(); } //------------------------------------------------------------------------------ void OAuthWizard::getOAuthAccess() { notifyMessage(tr("Authorising user ...")); connect(m_oam, SIGNAL(temporaryTokenReceived(QString, QString)), this, SLOT(onTemporaryTokenReceived(QString, QString))); connect(m_oam, SIGNAL(accessTokenReceived(QString, QString)), this, SLOT(onAccessTokenReceived(QString, QString))); m_oar->initRequest(KQOAuthRequest::TemporaryCredentials, QUrl(m_server+"/oauth/request_token")); m_oar->setConsumerKey(m_clientId); m_oar->setConsumerSecretKey(m_clientSecret); m_oam->executeRequest(m_oar); } //------------------------------------------------------------------------------ void OAuthWizard::onTemporaryTokenReceived(QString token, QString /*tokenSecret*/) { setField("token", token); QUrl userAuthURL(m_server+"/oauth/authorize"); if (m_oam->lastError() == KQOAuthManager::NoError) m_oam->getUserAuthorization(userAuthURL); else errorMessage(tr("Network or authentication error!")); } //------------------------------------------------------------------------------ void OAuthWizard::onSecondPageCommitted(QString token, QString verifier) { m_oam->verifyToken(token, verifier); m_oam->getUserAccessTokens(QUrl(m_server+"/oauth/access_token")); } //------------------------------------------------------------------------------ void OAuthWizard::onAccessTokenReceived(QString token, QString tokenSecret) { emit accessTokenReceived(token, tokenSecret); } pumpa-0.9.2/src/shortobjectwidget.h0000644000175000017500000000322012653206625016034 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _SHORTOBJECTWIDGET_H_ #define _SHORTOBJECTWIDGET_H_ #include #include #include "qactivitystreams.h" #include "texttoolbutton.h" #include "richtextlabel.h" #include "actorwidget.h" //------------------------------------------------------------------------------ class ShortObjectWidget : public QFrame { Q_OBJECT public: ShortObjectWidget(QASObject* obj, QWidget* parent = 0); virtual ~ShortObjectWidget(); virtual void changeObject(QASAbstractObject* obj); QASObject* object() const { return m_object; } virtual void refreshTimeLabels() {}; void updateMenu() { if (m_actorWidget) m_actorWidget->updateMenu(); } signals: void follow(QString, bool); void moreClicked(); private slots: void onChanged(); private: void updateText(); TextToolButton* m_moreButton; RichTextLabel* m_textLabel; ActorWidget* m_actorWidget; QASObject* m_object; QASActor* m_actor; }; #endif /* _SHORTOBJECTWIDGET_H_ */ pumpa-0.9.2/src/imagelabel.h0000644000175000017500000000211412653206625014365 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _IMAGELABEL_H_ #define _IMAGELABEL_H_ #include #include #include //------------------------------------------------------------------------------ class ImageLabel : public QLabel { Q_OBJECT public: ImageLabel(QWidget* parent=0); signals: void clicked(); protected: virtual void mousePressEvent(QMouseEvent*); }; #endif /* _IMAGELABEL_H_ */ pumpa-0.9.2/src/activitywidget.h0000644000175000017500000000360712653206625015353 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _ACTIVITYWIDGET_H_ #define _ACTIVITYWIDGET_H_ #include #include #include #include #include "richtextlabel.h" #include "qactivitystreams.h" #include "objectwidget.h" #include "objectwidgetwithsignals.h" //------------------------------------------------------------------------------ class ActivityWidget : public ObjectWidgetWithSignals { Q_OBJECT public: ActivityWidget(QASActivity* a, bool fullObject=true, QWidget* parent=0); virtual ~ActivityWidget(); void changeObject(QASAbstractObject* obj, bool fullObject); virtual void changeObject(QASAbstractObject* obj) { changeObject(obj, true); } virtual QString getId() const { return m_activity->id(); } QASActivity* activity() const { return m_activity; } virtual QASAbstractObject* asObject() const { return activity(); } virtual void refreshTimeLabels(); public slots: virtual void onObjectChanged(); signals: void showContext(QASObject*); private: void updateText(); QString recipientsToString(QASObjectList* rec); RichTextLabel* m_textLabel; ActorWidget* m_actorWidget; ObjectWidget* m_objectWidget; QASActivity* m_activity; }; #endif /* _ACTIVITYWIDGET_H_ */ pumpa-0.9.2/src/sundown/0000755000175000017500000000000012653206625013631 5ustar matsmatspumpa-0.9.2/src/sundown/houdini_html_e.c0000644000175000017500000000377512653206625017000 0ustar matsmats#include #include #include #include "houdini.h" #define ESCAPE_GROW_FACTOR(x) (((x) * 12) / 10) /* this is very scientific, yes */ /** * According to the OWASP rules: * * & --> & * < --> < * > --> > * " --> " * ' --> ' ' is not recommended * / --> / forward slash is included as it helps end an HTML entity * */ static const char HTML_ESCAPE_TABLE[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static const char *HTML_ESCAPES[] = { "", """, "&", "'", "/", "<", ">" }; void houdini_escape_html0(struct buf *ob, const uint8_t *src, size_t size, int secure) { size_t i = 0, org, esc = 0; bufgrow(ob, ESCAPE_GROW_FACTOR(size)); while (i < size) { org = i; while (i < size && (esc = HTML_ESCAPE_TABLE[src[i]]) == 0) i++; if (i > org) bufput(ob, src + org, i - org); /* escaping */ if (i >= size) break; /* The forward slash is only escaped in secure mode */ if (src[i] == '/' && !secure) { bufputc(ob, '/'); } else { bufputs(ob, HTML_ESCAPES[esc]); } i++; } } void houdini_escape_html(struct buf *ob, const uint8_t *src, size_t size) { houdini_escape_html0(ob, src, size, 1); } pumpa-0.9.2/src/sundown/stack.h0000644000175000017500000000064212653206625015111 0ustar matsmats#ifndef STACK_H__ #define STACK_H__ #include #ifdef __cplusplus extern "C" { #endif struct stack { void **item; size_t size; size_t asize; }; void stack_free(struct stack *); int stack_grow(struct stack *, size_t); int stack_init(struct stack *, size_t); int stack_push(struct stack *, void *); void *stack_pop(struct stack *); void *stack_top(struct stack *); #ifdef __cplusplus } #endif #endif pumpa-0.9.2/src/sundown/stack.c0000644000175000017500000000210412653206625015077 0ustar matsmats#include "stack.h" #include int stack_grow(struct stack *st, size_t new_size) { void **new_st; if (st->asize >= new_size) return 0; new_st = realloc(st->item, new_size * sizeof(void *)); if (new_st == NULL) return -1; memset(new_st + st->asize, 0x0, (new_size - st->asize) * sizeof(void *)); st->item = new_st; st->asize = new_size; if (st->size > new_size) st->size = new_size; return 0; } void stack_free(struct stack *st) { if (!st) return; free(st->item); st->item = NULL; st->size = 0; st->asize = 0; } int stack_init(struct stack *st, size_t initial_size) { st->item = NULL; st->size = 0; st->asize = 0; if (!initial_size) initial_size = 8; return stack_grow(st, initial_size); } void * stack_pop(struct stack *st) { if (!st->size) return NULL; return st->item[--st->size]; } int stack_push(struct stack *st, void *item) { if (stack_grow(st, st->size * 2) < 0) return -1; st->item[st->size++] = item; return 0; } void * stack_top(struct stack *st) { if (!st->size) return NULL; return st->item[st->size - 1]; } pumpa-0.9.2/src/sundown/html_smartypants.c0000644000175000017500000002514212653206625017412 0ustar matsmats/* * Copyright (c) 2011, Vicent Marti * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "buffer.h" #include "html.h" #include #include #include #include #if defined(_WIN32) #define snprintf _snprintf #endif struct smartypants_data { int in_squote; int in_dquote; }; static size_t smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size); static size_t (*smartypants_cb_ptrs[]) (struct buf *, struct smartypants_data *, uint8_t, const uint8_t *, size_t) = { NULL, /* 0 */ smartypants_cb__dash, /* 1 */ smartypants_cb__parens, /* 2 */ smartypants_cb__squote, /* 3 */ smartypants_cb__dquote, /* 4 */ smartypants_cb__amp, /* 5 */ smartypants_cb__period, /* 6 */ smartypants_cb__number, /* 7 */ smartypants_cb__ltag, /* 8 */ smartypants_cb__backtick, /* 9 */ smartypants_cb__escape, /* 10 */ }; static const uint8_t smartypants_cb_chars[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 5, 3, 2, 0, 0, 0, 0, 1, 6, 0, 0, 7, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static inline int word_boundary(uint8_t c) { return c == 0 || isspace(c) || ispunct(c); } static int smartypants_quotes(struct buf *ob, uint8_t previous_char, uint8_t next_char, uint8_t quote, int *is_open) { char ent[8]; if (*is_open && !word_boundary(next_char)) return 0; if (!(*is_open) && !word_boundary(previous_char)) return 0; snprintf(ent, sizeof(ent), "&%c%cquo;", (*is_open) ? 'r' : 'l', quote); *is_open = !(*is_open); bufputs(ob, ent); return 1; } static size_t smartypants_cb__squote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (size >= 2) { uint8_t t1 = tolower(text[1]); if (t1 == '\'') { if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote)) return 1; } if ((t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (size == 3 || word_boundary(text[2]))) { BUFPUTSL(ob, "’"); return 0; } if (size >= 3) { uint8_t t2 = tolower(text[2]); if (((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) && (size == 4 || word_boundary(text[3]))) { BUFPUTSL(ob, "’"); return 0; } } } if (smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 's', &smrt->in_squote)) return 0; bufputc(ob, text[0]); return 0; } static size_t smartypants_cb__parens(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (size >= 3) { uint8_t t1 = tolower(text[1]); uint8_t t2 = tolower(text[2]); if (t1 == 'c' && t2 == ')') { BUFPUTSL(ob, "©"); return 2; } if (t1 == 'r' && t2 == ')') { BUFPUTSL(ob, "®"); return 2; } if (size >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')') { BUFPUTSL(ob, "™"); return 3; } } bufputc(ob, text[0]); return 0; } static size_t smartypants_cb__dash(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (size >= 3 && text[1] == '-' && text[2] == '-') { BUFPUTSL(ob, "—"); return 2; } if (size >= 2 && text[1] == '-') { BUFPUTSL(ob, "–"); return 1; } bufputc(ob, text[0]); return 0; } static size_t smartypants_cb__amp(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (size >= 6 && memcmp(text, """, 6) == 0) { if (smartypants_quotes(ob, previous_char, size >= 7 ? text[6] : 0, 'd', &smrt->in_dquote)) return 5; } if (size >= 4 && memcmp(text, "�", 4) == 0) return 3; bufputc(ob, '&'); return 0; } static size_t smartypants_cb__period(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (size >= 3 && text[1] == '.' && text[2] == '.') { BUFPUTSL(ob, "…"); return 2; } if (size >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.') { BUFPUTSL(ob, "…"); return 4; } bufputc(ob, text[0]); return 0; } static size_t smartypants_cb__backtick(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (size >= 2 && text[1] == '`') { if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote)) return 1; } return 0; } static size_t smartypants_cb__number(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (word_boundary(previous_char) && size >= 3) { if (text[0] == '1' && text[1] == '/' && text[2] == '2') { if (size == 3 || word_boundary(text[3])) { BUFPUTSL(ob, "½"); return 2; } } if (text[0] == '1' && text[1] == '/' && text[2] == '4') { if (size == 3 || word_boundary(text[3]) || (size >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h')) { BUFPUTSL(ob, "¼"); return 2; } } if (text[0] == '3' && text[1] == '/' && text[2] == '4') { if (size == 3 || word_boundary(text[3]) || (size >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's')) { BUFPUTSL(ob, "¾"); return 2; } } } bufputc(ob, text[0]); return 0; } static size_t smartypants_cb__dquote(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (!smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 'd', &smrt->in_dquote)) BUFPUTSL(ob, """); return 0; } static size_t smartypants_cb__ltag(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { static const char *skip_tags[] = { "pre", "code", "var", "samp", "kbd", "math", "script", "style" }; static const size_t skip_tags_count = 8; size_t tag, i = 0; while (i < size && text[i] != '>') i++; for (tag = 0; tag < skip_tags_count; ++tag) { if (sdhtml_is_tag(text, size, skip_tags[tag]) == HTML_TAG_OPEN) break; } if (tag < skip_tags_count) { for (;;) { while (i < size && text[i] != '<') i++; if (i == size) break; if (sdhtml_is_tag(text + i, size - i, skip_tags[tag]) == HTML_TAG_CLOSE) break; i++; } while (i < size && text[i] != '>') i++; } bufput(ob, text, i + 1); return i; } static size_t smartypants_cb__escape(struct buf *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size) { if (size < 2) return 0; switch (text[1]) { case '\\': case '"': case '\'': case '.': case '-': case '`': bufputc(ob, text[1]); return 1; default: bufputc(ob, '\\'); return 0; } } #if 0 static struct { uint8_t c0; const uint8_t *pattern; const uint8_t *entity; int skip; } smartypants_subs[] = { { '\'', "'s>", "’", 0 }, { '\'', "'t>", "’", 0 }, { '\'', "'re>", "’", 0 }, { '\'', "'ll>", "’", 0 }, { '\'', "'ve>", "’", 0 }, { '\'', "'m>", "’", 0 }, { '\'', "'d>", "’", 0 }, { '-', "--", "—", 1 }, { '-', "<->", "–", 0 }, { '.', "...", "…", 2 }, { '.', ". . .", "…", 4 }, { '(', "(c)", "©", 2 }, { '(', "(r)", "®", 2 }, { '(', "(tm)", "™", 3 }, { '3', "<3/4>", "¾", 2 }, { '3', "<3/4ths>", "¾", 2 }, { '1', "<1/2>", "½", 2 }, { '1', "<1/4>", "¼", 2 }, { '1', "<1/4th>", "¼", 2 }, { '&', "�", 0, 3 }, }; #endif void sdhtml_smartypants(struct buf *ob, const uint8_t *text, size_t size) { size_t i; struct smartypants_data smrt = {0, 0}; if (!text) return; bufgrow(ob, size); for (i = 0; i < size; ++i) { size_t org; uint8_t action = 0; org = i; while (i < size && (action = smartypants_cb_chars[text[i]]) == 0) i++; if (i > org) bufput(ob, text + org, i - org); if (i < size) { i += smartypants_cb_ptrs[(int)action] (ob, &smrt, i ? text[i - 1] : 0, text + i, size - i); } } } pumpa-0.9.2/src/sundown/autolink.c0000644000175000017500000001407112653206625015626 0ustar matsmats/* * Copyright (c) 2011, Vicent Marti * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "buffer.h" #include "autolink.h" #include #include #include #include #pragma GCC diagnostic ignored "-Wunused-parameter" #if defined(_WIN32) #define strncasecmp _strnicmp #endif int sd_autolink_issafe(const uint8_t *link, size_t link_len) { static const size_t valid_uris_count = 5; static const char *valid_uris[] = { "/", "http://", "https://", "ftp://", "mailto:" }; size_t i; for (i = 0; i < valid_uris_count; ++i) { size_t len = strlen(valid_uris[i]); if (link_len > len && strncasecmp((char *)link, valid_uris[i], len) == 0 && isalnum(link[len])) return 1; } return 0; } static size_t autolink_delim(uint8_t *data, size_t link_end, size_t max_rewind, size_t size) { uint8_t cclose, copen = 0; size_t i; for (i = 0; i < link_end; ++i) if (data[i] == '<') { link_end = i; break; } while (link_end > 0) { if (strchr("?!.,", data[link_end - 1]) != NULL) link_end--; else if (data[link_end - 1] == ';') { size_t new_end = link_end - 2; while (new_end > 0 && isalpha(data[new_end])) new_end--; if (new_end < link_end - 2 && data[new_end] == '&') link_end = new_end; else link_end--; } else break; } if (link_end == 0) return 0; cclose = data[link_end - 1]; switch (cclose) { case '"': copen = '"'; break; case '\'': copen = '\''; break; case ')': copen = '('; break; case ']': copen = '['; break; case '}': copen = '{'; break; } if (copen != 0) { size_t closing = 0; size_t opening = 0; size_t i = 0; /* Try to close the final punctuation sign in this same line; * if we managed to close it outside of the URL, that means that it's * not part of the URL. If it closes inside the URL, that means it * is part of the URL. * * Examples: * * foo http://www.pokemon.com/Pikachu_(Electric) bar * => http://www.pokemon.com/Pikachu_(Electric) * * foo (http://www.pokemon.com/Pikachu_(Electric)) bar * => http://www.pokemon.com/Pikachu_(Electric) * * foo http://www.pokemon.com/Pikachu_(Electric)) bar * => http://www.pokemon.com/Pikachu_(Electric)) * * (foo http://www.pokemon.com/Pikachu_(Electric)) bar * => foo http://www.pokemon.com/Pikachu_(Electric) */ while (i < link_end) { if (data[i] == copen) opening++; else if (data[i] == cclose) closing++; i++; } if (closing != opening) link_end--; } return link_end; } static size_t check_domain(uint8_t *data, size_t size, int allow_short) { size_t i, np = 0; if (!isalnum(data[0])) return 0; for (i = 1; i < size - 1; ++i) { if (data[i] == '.') np++; else if (!isalnum(data[i]) && data[i] != '-') break; } if (allow_short) { /* We don't need a valid domain in the strict sense (with * least one dot; so just make sure it's composed of valid * domain characters and return the length of the the valid * sequence. */ return i; } else { /* a valid domain needs to have at least a dot. * that's as far as we get */ return np ? i : 0; } } size_t sd_autolink__www( size_t *rewind_p, struct buf *link, uint8_t *data, size_t max_rewind, size_t size, unsigned int flags) { size_t link_end; if (max_rewind > 0 && !ispunct(data[-1]) && !isspace(data[-1])) return 0; if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0) return 0; link_end = check_domain(data, size, 0); if (link_end == 0) return 0; while (link_end < size && !isspace(data[link_end])) link_end++; link_end = autolink_delim(data, link_end, max_rewind, size); if (link_end == 0) return 0; bufput(link, data, link_end); *rewind_p = 0; return (int)link_end; } size_t sd_autolink__email( size_t *rewind_p, struct buf *link, uint8_t *data, size_t max_rewind, size_t size, unsigned int flags) { size_t link_end, rewind; int nb = 0, np = 0; for (rewind = 0; rewind < max_rewind; ++rewind) { uint8_t c = data[-rewind - 1]; if (isalnum(c)) continue; if (strchr(".+-_", c) != NULL) continue; break; } if (rewind == 0) return 0; for (link_end = 0; link_end < size; ++link_end) { uint8_t c = data[link_end]; if (isalnum(c)) continue; if (c == '@') nb++; else if (c == '.' && link_end < size - 1) np++; else if (c != '-' && c != '_') break; } if (link_end < 2 || nb != 1 || np == 0 || !isalpha(data[link_end - 1])) return 0; link_end = autolink_delim(data, link_end, max_rewind, size); if (link_end == 0) return 0; bufput(link, data - rewind, link_end + rewind); *rewind_p = rewind; return link_end; } size_t sd_autolink__url( size_t *rewind_p, struct buf *link, uint8_t *data, size_t max_rewind, size_t size, unsigned int flags) { size_t link_end, rewind = 0, domain_len; if (size < 4 || data[1] != '/' || data[2] != '/') return 0; while (rewind < max_rewind && isalpha(data[-rewind - 1])) rewind++; if (!sd_autolink_issafe(data - rewind, size + rewind)) return 0; link_end = strlen("://"); domain_len = check_domain( data + link_end, size - link_end, flags & SD_AUTOLINK_SHORT_DOMAINS); if (domain_len == 0) return 0; link_end += domain_len; while (link_end < size && !isspace(data[link_end])) link_end++; link_end = autolink_delim(data, link_end, max_rewind, size); if (link_end == 0) return 0; bufput(link, data - rewind, link_end + rewind); *rewind_p = rewind; return link_end; } pumpa-0.9.2/src/sundown/html.h0000644000175000017500000000367512653206625014761 0ustar matsmats/* * Copyright (c) 2011, Vicent Marti * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef UPSKIRT_HTML_H #define UPSKIRT_HTML_H #include "markdown.h" #include "buffer.h" #include #ifdef __cplusplus extern "C" { #endif struct html_renderopt { struct { int header_count; int current_level; int level_offset; } toc_data; unsigned int flags; /* extra callbacks */ void (*link_attributes)(struct buf *ob, const struct buf *url, void *self); }; typedef enum { HTML_SKIP_HTML = (1 << 0), HTML_SKIP_STYLE = (1 << 1), HTML_SKIP_IMAGES = (1 << 2), HTML_SKIP_LINKS = (1 << 3), HTML_EXPAND_TABS = (1 << 4), HTML_SAFELINK = (1 << 5), HTML_TOC = (1 << 6), HTML_HARD_WRAP = (1 << 7), HTML_USE_XHTML = (1 << 8), HTML_ESCAPE = (1 << 9), } html_render_mode; typedef enum { HTML_TAG_NONE = 0, HTML_TAG_OPEN, HTML_TAG_CLOSE, } html_tag; int sdhtml_is_tag(const uint8_t *tag_data, size_t tag_size, const char *tagname); extern void sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr, unsigned int render_flags); extern void sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr); extern void sdhtml_smartypants(struct buf *ob, const uint8_t *text, size_t size); #ifdef __cplusplus } #endif #endif pumpa-0.9.2/src/sundown/Makefile0000644000175000017500000000003012653206625015262 0ustar matsmatsall: $(MAKE) -C ../../ pumpa-0.9.2/src/sundown/houdini_href_e.c0000644000175000017500000000560612653206625016753 0ustar matsmats#include #include #include #include "houdini.h" #define ESCAPE_GROW_FACTOR(x) (((x) * 12) / 10) /* * The following characters will not be escaped: * * -_.+!*'(),%#@?=;:/,+&$ alphanum * * Note that this character set is the addition of: * * - The characters which are safe to be in an URL * - The characters which are *not* safe to be in * an URL because they are RESERVED characters. * * We asume (lazily) that any RESERVED char that * appears inside an URL is actually meant to * have its native function (i.e. as an URL * component/separator) and hence needs no escaping. * * There are two exceptions: the chacters & (amp) * and ' (single quote) do not appear in the table. * They are meant to appear in the URL as components, * yet they require special HTML-entity escaping * to generate valid HTML markup. * * All other characters will be escaped to %XX. * */ static const char HREF_SAFE[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; void houdini_escape_href(struct buf *ob, const uint8_t *src, size_t size) { static const char hex_chars[] = "0123456789ABCDEF"; size_t i = 0, org; char hex_str[3]; bufgrow(ob, ESCAPE_GROW_FACTOR(size)); hex_str[0] = '%'; while (i < size) { org = i; while (i < size && HREF_SAFE[src[i]] != 0) i++; if (i > org) bufput(ob, src + org, i - org); /* escaping */ if (i >= size) break; switch (src[i]) { /* amp appears all the time in URLs, but needs * HTML-entity escaping to be inside an href */ case '&': BUFPUTSL(ob, "&"); break; /* the single quote is a valid URL character * according to the standard; it needs HTML * entity escaping too */ case '\'': BUFPUTSL(ob, "'"); break; /* the space can be escaped to %20 or a plus * sign. we're going with the generic escape * for now. the plus thing is more commonly seen * when building GET strings */ #if 0 case ' ': bufputc(ob, '+'); break; #endif /* every other character goes with a %XX escaping */ default: hex_str[1] = hex_chars[(src[i] >> 4) & 0xF]; hex_str[2] = hex_chars[src[i] & 0xF]; bufput(ob, hex_str, 3); } i++; } } pumpa-0.9.2/src/sundown/html_blocks.h0000644000175000017500000001532212653206625016306 0ustar matsmats/* C code produced by gperf version 3.0.3 */ /* Command-line: gperf -N find_block_tag -H hash_block_tag -C -c -E --ignore-case html_block_names.txt */ /* Computed positions: -k'1-2' */ #if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) /* The character set is not based on ISO-646. */ error "gperf generated tables don't work with this execution character set. Please report a bug to ." #endif /* maximum key range = 37, duplicates = 0 */ #ifndef GPERF_DOWNCASE #define GPERF_DOWNCASE 1 static unsigned char gperf_downcase[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 }; #endif #ifndef GPERF_CASE_STRNCMP #define GPERF_CASE_STRNCMP 1 static int gperf_case_strncmp (s1, s2, n) register const char *s1; register const char *s2; register unsigned int n; { for (; n > 0;) { unsigned char c1 = gperf_downcase[(unsigned char)*s1++]; unsigned char c2 = gperf_downcase[(unsigned char)*s2++]; if (c1 != 0 && c1 == c2) { n--; continue; } return (int)c1 - (int)c2; } return 0; } #endif #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static unsigned int hash_block_tag (str, len) register const char *str; register unsigned int len; { static const unsigned char asso_values[] = { 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 8, 30, 25, 20, 15, 10, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 0, 38, 0, 38, 5, 5, 5, 15, 0, 38, 38, 0, 15, 10, 0, 38, 38, 15, 0, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 0, 38, 0, 38, 5, 5, 5, 15, 0, 38, 38, 0, 15, 10, 0, 38, 38, 15, 0, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38 }; register int hval = len; switch (hval) { default: hval += asso_values[(unsigned char)str[1]+1]; /*FALLTHROUGH*/ case 1: hval += asso_values[(unsigned char)str[0]]; break; } return hval; } #ifdef __GNUC__ __inline #ifdef __GNUC_STDC_INLINE__ __attribute__ ((__gnu_inline__)) #endif #endif const char * find_block_tag (str, len) register const char *str; register unsigned int len; { enum { TOTAL_KEYWORDS = 24, MIN_WORD_LENGTH = 1, MAX_WORD_LENGTH = 10, MIN_HASH_VALUE = 1, MAX_HASH_VALUE = 37 }; static const char * const wordlist[] = { "", "p", "dl", "div", "math", "table", "", "ul", "del", "form", "blockquote", "figure", "ol", "fieldset", "", "h1", "", "h6", "pre", "", "", "script", "h5", "noscript", "", "style", "iframe", "h4", "ins", "", "", "", "h3", "", "", "", "", "h2" }; if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) { register int key = hash_block_tag (str, len); if (key <= MAX_HASH_VALUE && key >= 0) { register const char *s = wordlist[key]; if ((((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strncmp (str, s, len) && s[len] == '\0') return s; } } return 0; } pumpa-0.9.2/src/sundown/markdown.c0000644000175000017500000016345512653206625015635 0ustar matsmats/* markdown.c - generic markdown parser */ /* * Copyright (c) 2009, Natacha Porté * Copyright (c) 2011, Vicent Marti * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "markdown.h" #include "stack.h" #include #include #include #include #pragma GCC diagnostic ignored "-Wunused-parameter" #if defined(_WIN32) #define strncasecmp _strnicmp #endif #define REF_TABLE_SIZE 8 #define BUFFER_BLOCK 0 #define BUFFER_SPAN 1 #define MKD_LI_END 8 /* internal list flag */ #define gperf_case_strncmp(s1, s2, n) strncasecmp(s1, s2, n) #define GPERF_DOWNCASE 1 #define GPERF_CASE_STRNCMP 1 #include "html_blocks.h" /*************** * LOCAL TYPES * ***************/ /* link_ref: reference to a link */ struct link_ref { unsigned int id; struct buf *link; struct buf *title; struct link_ref *next; }; /* char_trigger: function pointer to render active chars */ /* returns the number of chars taken care of */ /* data is the pointer of the beginning of the span */ /* offset is the number of valid chars before data */ struct sd_markdown; typedef size_t (*char_trigger)(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_emphasis(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_linebreak(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_codespan(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_escape(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_entity(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_langle_tag(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_autolink_url(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_autolink_email(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_autolink_www(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_link(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); static size_t char_superscript(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size); enum markdown_char_t { MD_CHAR_NONE = 0, MD_CHAR_EMPHASIS, MD_CHAR_CODESPAN, MD_CHAR_LINEBREAK, MD_CHAR_LINK, MD_CHAR_LANGLE, MD_CHAR_ESCAPE, MD_CHAR_ENTITITY, MD_CHAR_AUTOLINK_URL, MD_CHAR_AUTOLINK_EMAIL, MD_CHAR_AUTOLINK_WWW, MD_CHAR_SUPERSCRIPT, }; static char_trigger markdown_char_ptrs[] = { NULL, &char_emphasis, &char_codespan, &char_linebreak, &char_link, &char_langle_tag, &char_escape, &char_entity, &char_autolink_url, &char_autolink_email, &char_autolink_www, &char_superscript, }; /* render • structure containing one particular render */ struct sd_markdown { struct sd_callbacks cb; void *opaque; struct link_ref *refs[REF_TABLE_SIZE]; uint8_t active_char[256]; struct stack work_bufs[2]; unsigned int ext_flags; size_t max_nesting; int in_link_body; }; /*************************** * HELPER FUNCTIONS * ***************************/ static inline struct buf * rndr_newbuf(struct sd_markdown *rndr, int type) { static const size_t buf_size[2] = {256, 64}; struct buf *work = NULL; struct stack *pool = &rndr->work_bufs[type]; if (pool->size < pool->asize && pool->item[pool->size] != NULL) { work = pool->item[pool->size++]; work->size = 0; } else { work = bufnew(buf_size[type]); stack_push(pool, work); } return work; } static inline void rndr_popbuf(struct sd_markdown *rndr, int type) { rndr->work_bufs[type].size--; } static void unscape_text(struct buf *ob, struct buf *src) { size_t i = 0, org; while (i < src->size) { org = i; while (i < src->size && src->data[i] != '\\') i++; if (i > org) bufput(ob, src->data + org, i - org); if (i + 1 >= src->size) break; bufputc(ob, src->data[i + 1]); i += 2; } } static unsigned int hash_link_ref(const uint8_t *link_ref, size_t length) { size_t i; unsigned int hash = 0; for (i = 0; i < length; ++i) hash = tolower(link_ref[i]) + (hash << 6) + (hash << 16) - hash; return hash; } static struct link_ref * add_link_ref( struct link_ref **references, const uint8_t *name, size_t name_size) { struct link_ref *ref = calloc(1, sizeof(struct link_ref)); if (!ref) return NULL; ref->id = hash_link_ref(name, name_size); ref->next = references[ref->id % REF_TABLE_SIZE]; references[ref->id % REF_TABLE_SIZE] = ref; return ref; } static struct link_ref * find_link_ref(struct link_ref **references, uint8_t *name, size_t length) { unsigned int hash = hash_link_ref(name, length); struct link_ref *ref = NULL; ref = references[hash % REF_TABLE_SIZE]; while (ref != NULL) { if (ref->id == hash) return ref; ref = ref->next; } return NULL; } static void free_link_refs(struct link_ref **references) { size_t i; for (i = 0; i < REF_TABLE_SIZE; ++i) { struct link_ref *r = references[i]; struct link_ref *next; while (r) { next = r->next; bufrelease(r->link); bufrelease(r->title); free(r); r = next; } } } /* * Check whether a char is a Markdown space. * Right now we only consider spaces the actual * space and a newline: tabs and carriage returns * are filtered out during the preprocessing phase. * * If we wanted to actually be UTF-8 compliant, we * should instead extract an Unicode codepoint from * this character and check for space properties. */ static inline int _isspace(int c) { return c == ' ' || c == '\n'; } /**************************** * INLINE PARSING FUNCTIONS * ****************************/ /* is_mail_autolink • looks for the address part of a mail autolink and '>' */ /* this is less strict than the original markdown e-mail address matching */ static size_t is_mail_autolink(uint8_t *data, size_t size) { size_t i = 0, nb = 0; /* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */ for (i = 0; i < size; ++i) { if (isalnum(data[i])) continue; switch (data[i]) { case '@': nb++; case '-': case '.': case '_': break; case '>': return (nb == 1) ? i + 1 : 0; default: return 0; } } return 0; } /* tag_length • returns the length of the given tag, or 0 is it's not valid */ static size_t tag_length(uint8_t *data, size_t size, enum mkd_autolink *autolink) { size_t i, j; /* a valid tag can't be shorter than 3 chars */ if (size < 3) return 0; /* begins with a '<' optionally followed by '/', followed by letter or number */ if (data[0] != '<') return 0; i = (data[1] == '/') ? 2 : 1; if (!isalnum(data[i])) return 0; /* scheme test */ *autolink = MKDA_NOT_AUTOLINK; /* try to find the beginning of an URI */ while (i < size && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-')) i++; if (i > 1 && data[i] == '@') { if ((j = is_mail_autolink(data + i, size - i)) != 0) { *autolink = MKDA_EMAIL; return i + j; } } if (i > 2 && data[i] == ':') { *autolink = MKDA_NORMAL; i++; } /* completing autolink test: no whitespace or ' or " */ if (i >= size) *autolink = MKDA_NOT_AUTOLINK; else if (*autolink) { j = i; while (i < size) { if (data[i] == '\\') i += 2; else if (data[i] == '>' || data[i] == '\'' || data[i] == '"' || data[i] == ' ' || data[i] == '\n') break; else i++; } if (i >= size) return 0; if (i > j && data[i] == '>') return i + 1; /* one of the forbidden chars has been found */ *autolink = MKDA_NOT_AUTOLINK; } /* looking for sometinhg looking like a tag end */ while (i < size && data[i] != '>') i++; if (i >= size) return 0; return i + 1; } /* parse_inline • parses inline markdown elements */ static void parse_inline(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size) { size_t i = 0, end = 0; uint8_t action = 0; struct buf work = { 0, 0, 0, 0 }; if (rndr->work_bufs[BUFFER_SPAN].size + rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting) return; while (i < size) { /* copying inactive chars into the output */ while (end < size && (action = rndr->active_char[data[end]]) == 0) { end++; } if (rndr->cb.normal_text) { work.data = data + i; work.size = end - i; rndr->cb.normal_text(ob, &work, rndr->opaque); } else bufput(ob, data + i, end - i); if (end >= size) break; i = end; end = markdown_char_ptrs[(int)action](ob, rndr, data + i, i, size - i); if (!end) /* no action from the callback */ end = i + 1; else { i += end; end = i; } } } /* find_emph_char • looks for the next emph uint8_t, skipping other constructs */ static size_t find_emph_char(uint8_t *data, size_t size, uint8_t c) { size_t i = 1; while (i < size) { while (i < size && data[i] != c && data[i] != '`' && data[i] != '[') i++; if (i == size) return 0; if (data[i] == c) return i; /* not counting escaped chars */ if (i && data[i - 1] == '\\') { i++; continue; } if (data[i] == '`') { size_t span_nb = 0, bt; size_t tmp_i = 0; /* counting the number of opening backticks */ while (i < size && data[i] == '`') { i++; span_nb++; } if (i >= size) return 0; /* finding the matching closing sequence */ bt = 0; while (i < size && bt < span_nb) { if (!tmp_i && data[i] == c) tmp_i = i; if (data[i] == '`') bt++; else bt = 0; i++; } if (i >= size) return tmp_i; } /* skipping a link */ else if (data[i] == '[') { size_t tmp_i = 0; uint8_t cc; i++; while (i < size && data[i] != ']') { if (!tmp_i && data[i] == c) tmp_i = i; i++; } i++; while (i < size && (data[i] == ' ' || data[i] == '\n')) i++; if (i >= size) return tmp_i; switch (data[i]) { case '[': cc = ']'; break; case '(': cc = ')'; break; default: if (tmp_i) return tmp_i; else continue; } i++; while (i < size && data[i] != cc) { if (!tmp_i && data[i] == c) tmp_i = i; i++; } if (i >= size) return tmp_i; i++; } } return 0; } /* parse_emph1 • parsing single emphase */ /* closed by a symbol not preceded by whitespace and not followed by symbol */ static size_t parse_emph1(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, uint8_t c) { size_t i = 0, len; struct buf *work = 0; int r; if (!rndr->cb.emphasis) return 0; /* skipping one symbol if coming from emph3 */ if (size > 1 && data[0] == c && data[1] == c) i = 1; while (i < size) { len = find_emph_char(data + i, size - i, c); if (!len) return 0; i += len; if (i >= size) return 0; if (data[i] == c && !_isspace(data[i - 1])) { if (rndr->ext_flags & MKDEXT_NO_INTRA_EMPHASIS) { if (i + 1 < size && isalnum(data[i + 1])) continue; } work = rndr_newbuf(rndr, BUFFER_SPAN); parse_inline(work, rndr, data, i); r = rndr->cb.emphasis(ob, work, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); return r ? i + 1 : 0; } } return 0; } /* parse_emph2 • parsing single emphase */ static size_t parse_emph2(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, uint8_t c) { int (*render_method)(struct buf *ob, const struct buf *text, void *opaque); size_t i = 0, len; struct buf *work = 0; int r; render_method = (c == '~') ? rndr->cb.strikethrough : rndr->cb.double_emphasis; if (!render_method) return 0; while (i < size) { len = find_emph_char(data + i, size - i, c); if (!len) return 0; i += len; if (i + 1 < size && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) { work = rndr_newbuf(rndr, BUFFER_SPAN); parse_inline(work, rndr, data, i); r = render_method(ob, work, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); return r ? i + 2 : 0; } i++; } return 0; } /* parse_emph3 • parsing single emphase */ /* finds the first closing tag, and delegates to the other emph */ static size_t parse_emph3(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, uint8_t c) { size_t i = 0, len; int r; while (i < size) { len = find_emph_char(data + i, size - i, c); if (!len) return 0; i += len; /* skip whitespace preceded symbols */ if (data[i] != c || _isspace(data[i - 1])) continue; if (i + 2 < size && data[i + 1] == c && data[i + 2] == c && rndr->cb.triple_emphasis) { /* triple symbol found */ struct buf *work = rndr_newbuf(rndr, BUFFER_SPAN); parse_inline(work, rndr, data, i); r = rndr->cb.triple_emphasis(ob, work, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); return r ? i + 3 : 0; } else if (i + 1 < size && data[i + 1] == c) { /* double symbol found, handing over to emph1 */ len = parse_emph1(ob, rndr, data - 2, size + 2, c); if (!len) return 0; else return len - 2; } else { /* single symbol found, handing over to emph2 */ len = parse_emph2(ob, rndr, data - 1, size + 1, c); if (!len) return 0; else return len - 1; } } return 0; } /* char_emphasis • single and double emphasis parsing */ static size_t char_emphasis(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { uint8_t c = data[0]; size_t ret; if (rndr->ext_flags & MKDEXT_NO_INTRA_EMPHASIS) { if (offset > 0 && !_isspace(data[-1]) && data[-1] != '>') return 0; } if (size > 2 && data[1] != c) { /* whitespace cannot follow an opening emphasis; * strikethrough only takes two characters '~~' */ if (c == '~' || _isspace(data[1]) || (ret = parse_emph1(ob, rndr, data + 1, size - 1, c)) == 0) return 0; return ret + 1; } if (size > 3 && data[1] == c && data[2] != c) { if (_isspace(data[2]) || (ret = parse_emph2(ob, rndr, data + 2, size - 2, c)) == 0) return 0; return ret + 2; } if (size > 4 && data[1] == c && data[2] == c && data[3] != c) { if (c == '~' || _isspace(data[3]) || (ret = parse_emph3(ob, rndr, data + 3, size - 3, c)) == 0) return 0; return ret + 3; } return 0; } /* char_linebreak • '\n' preceded by two spaces (assuming linebreak != 0) */ static size_t char_linebreak(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { if (offset < 2 || data[-1] != ' ' || data[-2] != ' ') return 0; /* removing the last space from ob and rendering */ while (ob->size && ob->data[ob->size - 1] == ' ') ob->size--; return rndr->cb.linebreak(ob, rndr->opaque) ? 1 : 0; } /* char_codespan • '`' parsing a code span (assuming codespan != 0) */ static size_t char_codespan(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { size_t end, nb = 0, i, f_begin, f_end; /* counting the number of backticks in the delimiter */ while (nb < size && data[nb] == '`') nb++; /* finding the next delimiter */ i = 0; for (end = nb; end < size && i < nb; end++) { if (data[end] == '`') i++; else i = 0; } if (i < nb && end >= size) return 0; /* no matching delimiter */ /* trimming outside whitespaces */ f_begin = nb; while (f_begin < end && data[f_begin] == ' ') f_begin++; f_end = end - nb; while (f_end > nb && data[f_end-1] == ' ') f_end--; /* real code span */ if (f_begin < f_end) { struct buf work = { data + f_begin, f_end - f_begin, 0, 0 }; if (!rndr->cb.codespan(ob, &work, rndr->opaque)) end = 0; } else { if (!rndr->cb.codespan(ob, 0, rndr->opaque)) end = 0; } return end; } /* char_escape • '\\' backslash escape */ static size_t char_escape(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { static const char *escape_chars = "\\`*_{}[]()#+-.!:|&<>^~"; struct buf work = { 0, 0, 0, 0 }; if (size > 1) { if (strchr(escape_chars, data[1]) == NULL) return 0; if (rndr->cb.normal_text) { work.data = data + 1; work.size = 1; rndr->cb.normal_text(ob, &work, rndr->opaque); } else bufputc(ob, data[1]); } else if (size == 1) { bufputc(ob, data[0]); } return 2; } /* char_entity • '&' escaped when it doesn't belong to an entity */ /* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */ static size_t char_entity(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { size_t end = 1; struct buf work = { 0, 0, 0, 0 }; if (end < size && data[end] == '#') end++; while (end < size && isalnum(data[end])) end++; if (end < size && data[end] == ';') end++; /* real entity */ else return 0; /* lone '&' */ if (rndr->cb.entity) { work.data = data; work.size = end; rndr->cb.entity(ob, &work, rndr->opaque); } else bufput(ob, data, end); return end; } /* char_langle_tag • '<' when tags or autolinks are allowed */ static size_t char_langle_tag(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { enum mkd_autolink altype = MKDA_NOT_AUTOLINK; size_t end = tag_length(data, size, &altype); struct buf work = { data, end, 0, 0 }; int ret = 0; if (end > 2) { if (rndr->cb.autolink && altype != MKDA_NOT_AUTOLINK) { struct buf *u_link = rndr_newbuf(rndr, BUFFER_SPAN); work.data = data + 1; work.size = end - 2; unscape_text(u_link, &work); ret = rndr->cb.autolink(ob, u_link, altype, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); } else if (rndr->cb.raw_html_tag) ret = rndr->cb.raw_html_tag(ob, &work, rndr->opaque); } if (!ret) return 0; else return end; } static size_t char_autolink_www(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { struct buf *link, *link_url, *link_text; size_t link_len, rewind; if (!rndr->cb.link || rndr->in_link_body) return 0; link = rndr_newbuf(rndr, BUFFER_SPAN); if ((link_len = sd_autolink__www(&rewind, link, data, offset, size, 0)) > 0) { link_url = rndr_newbuf(rndr, BUFFER_SPAN); BUFPUTSL(link_url, "http://"); bufput(link_url, link->data, link->size); ob->size -= rewind; if (rndr->cb.normal_text) { link_text = rndr_newbuf(rndr, BUFFER_SPAN); rndr->cb.normal_text(link_text, link, rndr->opaque); rndr->cb.link(ob, link_url, NULL, link_text, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); } else { rndr->cb.link(ob, link_url, NULL, link, rndr->opaque); } rndr_popbuf(rndr, BUFFER_SPAN); } rndr_popbuf(rndr, BUFFER_SPAN); return link_len; } static size_t char_autolink_email(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { struct buf *link; size_t link_len, rewind; if (!rndr->cb.autolink || rndr->in_link_body) return 0; link = rndr_newbuf(rndr, BUFFER_SPAN); if ((link_len = sd_autolink__email(&rewind, link, data, offset, size, 0)) > 0) { ob->size -= rewind; rndr->cb.autolink(ob, link, MKDA_EMAIL, rndr->opaque); } rndr_popbuf(rndr, BUFFER_SPAN); return link_len; } static size_t char_autolink_url(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { struct buf *link; size_t link_len, rewind; if (!rndr->cb.autolink || rndr->in_link_body) return 0; link = rndr_newbuf(rndr, BUFFER_SPAN); if ((link_len = sd_autolink__url(&rewind, link, data, offset, size, 0)) > 0) { ob->size -= rewind; rndr->cb.autolink(ob, link, MKDA_NORMAL, rndr->opaque); } rndr_popbuf(rndr, BUFFER_SPAN); return link_len; } /* char_link • '[': parsing a link or an image */ static size_t char_link(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { int is_img = (offset && data[-1] == '!'), level; size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0; struct buf *content = 0; struct buf *link = 0; struct buf *title = 0; struct buf *u_link = 0; size_t org_work_size = rndr->work_bufs[BUFFER_SPAN].size; int text_has_nl = 0, ret = 0; int in_title = 0, qtype = 0; /* checking whether the correct renderer exists */ if ((is_img && !rndr->cb.image) || (!is_img && !rndr->cb.link)) goto cleanup; /* looking for the matching closing bracket */ for (level = 1; i < size; i++) { if (data[i] == '\n') text_has_nl = 1; else if (data[i - 1] == '\\') continue; else if (data[i] == '[') level++; else if (data[i] == ']') { level--; if (level <= 0) break; } } if (i >= size) goto cleanup; txt_e = i; i++; /* skip any amount of whitespace or newline */ /* (this is much more laxist than original markdown syntax) */ while (i < size && _isspace(data[i])) i++; /* inline style link */ if (i < size && data[i] == '(') { /* skipping initial whitespace */ i++; while (i < size && _isspace(data[i])) i++; link_b = i; /* looking for link end: ' " ) */ while (i < size) { if (data[i] == '\\') i += 2; else if (data[i] == ')') break; else if (i >= 1 && _isspace(data[i-1]) && (data[i] == '\'' || data[i] == '"')) break; else i++; } if (i >= size) goto cleanup; link_e = i; /* looking for title end if present */ if (data[i] == '\'' || data[i] == '"') { qtype = data[i]; in_title = 1; i++; title_b = i; while (i < size) { if (data[i] == '\\') i += 2; else if (data[i] == qtype) {in_title = 0; i++;} else if ((data[i] == ')') && !in_title) break; else i++; } if (i >= size) goto cleanup; /* skipping whitespaces after title */ title_e = i - 1; while (title_e > title_b && _isspace(data[title_e])) title_e--; /* checking for closing quote presence */ if (data[title_e] != '\'' && data[title_e] != '"') { title_b = title_e = 0; link_e = i; } } /* remove whitespace at the end of the link */ while (link_e > link_b && _isspace(data[link_e - 1])) link_e--; /* remove optional angle brackets around the link */ if (data[link_b] == '<') link_b++; if (data[link_e - 1] == '>') link_e--; /* building escaped link and title */ if (link_e > link_b) { link = rndr_newbuf(rndr, BUFFER_SPAN); bufput(link, data + link_b, link_e - link_b); } if (title_e > title_b) { title = rndr_newbuf(rndr, BUFFER_SPAN); bufput(title, data + title_b, title_e - title_b); } i++; } /* reference style link */ else if (i < size && data[i] == '[') { struct buf id = { 0, 0, 0, 0 }; struct link_ref *lr; /* looking for the id */ i++; link_b = i; while (i < size && data[i] != ']') i++; if (i >= size) goto cleanup; link_e = i; /* finding the link_ref */ if (link_b == link_e) { if (text_has_nl) { struct buf *b = rndr_newbuf(rndr, BUFFER_SPAN); size_t j; for (j = 1; j < txt_e; j++) { if (data[j] != '\n') bufputc(b, data[j]); else if (data[j - 1] != ' ') bufputc(b, ' '); } id.data = b->data; id.size = b->size; } else { id.data = data + 1; id.size = txt_e - 1; } } else { id.data = data + link_b; id.size = link_e - link_b; } lr = find_link_ref(rndr->refs, id.data, id.size); if (!lr) goto cleanup; /* keeping link and title from link_ref */ link = lr->link; title = lr->title; i++; } /* shortcut reference style link */ else { struct buf id = { 0, 0, 0, 0 }; struct link_ref *lr; /* crafting the id */ if (text_has_nl) { struct buf *b = rndr_newbuf(rndr, BUFFER_SPAN); size_t j; for (j = 1; j < txt_e; j++) { if (data[j] != '\n') bufputc(b, data[j]); else if (data[j - 1] != ' ') bufputc(b, ' '); } id.data = b->data; id.size = b->size; } else { id.data = data + 1; id.size = txt_e - 1; } /* finding the link_ref */ lr = find_link_ref(rndr->refs, id.data, id.size); if (!lr) goto cleanup; /* keeping link and title from link_ref */ link = lr->link; title = lr->title; /* rewinding the whitespace */ i = txt_e + 1; } /* building content: img alt is escaped, link content is parsed */ if (txt_e > 1) { content = rndr_newbuf(rndr, BUFFER_SPAN); if (is_img) { bufput(content, data + 1, txt_e - 1); } else { /* disable autolinking when parsing inline the * content of a link */ rndr->in_link_body = 1; parse_inline(content, rndr, data + 1, txt_e - 1); rndr->in_link_body = 0; } } if (link) { u_link = rndr_newbuf(rndr, BUFFER_SPAN); unscape_text(u_link, link); } /* calling the relevant rendering function */ if (is_img) { if (ob->size && ob->data[ob->size - 1] == '!') ob->size -= 1; ret = rndr->cb.image(ob, u_link, title, content, rndr->opaque); } else { ret = rndr->cb.link(ob, u_link, title, content, rndr->opaque); } /* cleanup */ cleanup: rndr->work_bufs[BUFFER_SPAN].size = (int)org_work_size; return ret ? i : 0; } static size_t char_superscript(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset, size_t size) { size_t sup_start, sup_len; struct buf *sup; if (!rndr->cb.superscript) return 0; if (size < 2) return 0; if (data[1] == '(') { sup_start = sup_len = 2; while (sup_len < size && data[sup_len] != ')' && data[sup_len - 1] != '\\') sup_len++; if (sup_len == size) return 0; } else { sup_start = sup_len = 1; while (sup_len < size && !_isspace(data[sup_len])) sup_len++; } if (sup_len - sup_start == 0) return (sup_start == 2) ? 3 : 0; sup = rndr_newbuf(rndr, BUFFER_SPAN); parse_inline(sup, rndr, data + sup_start, sup_len - sup_start); rndr->cb.superscript(ob, sup, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); return (sup_start == 2) ? sup_len + 1 : sup_len; } /********************************* * BLOCK-LEVEL PARSING FUNCTIONS * *********************************/ /* is_empty • returns the line length when it is empty, 0 otherwise */ static size_t is_empty(uint8_t *data, size_t size) { size_t i; for (i = 0; i < size && data[i] != '\n'; i++) if (data[i] != ' ') return 0; return i + 1; } /* is_hrule • returns whether a line is a horizontal rule */ static int is_hrule(uint8_t *data, size_t size) { size_t i = 0, n = 0; uint8_t c; /* skipping initial spaces */ if (size < 3) return 0; if (data[0] == ' ') { i++; if (data[1] == ' ') { i++; if (data[2] == ' ') { i++; } } } /* looking at the hrule uint8_t */ if (i + 2 >= size || (data[i] != '*' && data[i] != '-' && data[i] != '_')) return 0; c = data[i]; /* the whole line must be the char or whitespace */ while (i < size && data[i] != '\n') { if (data[i] == c) n++; else if (data[i] != ' ') return 0; i++; } return n >= 3; } /* check if a line begins with a code fence; return the * width of the code fence */ static size_t prefix_codefence(uint8_t *data, size_t size) { size_t i = 0, n = 0; uint8_t c; /* skipping initial spaces */ if (size < 3) return 0; if (data[0] == ' ') { i++; if (data[1] == ' ') { i++; if (data[2] == ' ') { i++; } } } /* looking at the hrule uint8_t */ if (i + 2 >= size || !(data[i] == '~' || data[i] == '`')) return 0; c = data[i]; /* the whole line must be the uint8_t or whitespace */ while (i < size && data[i] == c) { n++; i++; } if (n < 3) return 0; return i; } /* check if a line is a code fence; return its size if it is */ static size_t is_codefence(uint8_t *data, size_t size, struct buf *syntax) { size_t i = 0, syn_len = 0; uint8_t *syn_start; i = prefix_codefence(data, size); if (i == 0) return 0; while (i < size && data[i] == ' ') i++; syn_start = data + i; if (i < size && data[i] == '{') { i++; syn_start++; while (i < size && data[i] != '}' && data[i] != '\n') { syn_len++; i++; } if (i == size || data[i] != '}') return 0; /* strip all whitespace at the beginning and the end * of the {} block */ while (syn_len > 0 && _isspace(syn_start[0])) { syn_start++; syn_len--; } while (syn_len > 0 && _isspace(syn_start[syn_len - 1])) syn_len--; i++; } else { while (i < size && !_isspace(data[i])) { syn_len++; i++; } } if (syntax) { syntax->data = syn_start; syntax->size = syn_len; } while (i < size && data[i] != '\n') { if (!_isspace(data[i])) return 0; i++; } return i + 1; } /* is_atxheader • returns whether the line is a hash-prefixed header */ static int is_atxheader(struct sd_markdown *rndr, uint8_t *data, size_t size) { if (data[0] != '#') return 0; if (rndr->ext_flags & MKDEXT_SPACE_HEADERS) { size_t level = 0; while (level < size && level < 6 && data[level] == '#') level++; if (level < size && data[level] != ' ') return 0; } return 1; } /* is_headerline • returns whether the line is a setext-style hdr underline */ static int is_headerline(uint8_t *data, size_t size) { size_t i = 0; /* test of level 1 header */ if (data[i] == '=') { for (i = 1; i < size && data[i] == '='; i++); while (i < size && data[i] == ' ') i++; return (i >= size || data[i] == '\n') ? 1 : 0; } /* test of level 2 header */ if (data[i] == '-') { for (i = 1; i < size && data[i] == '-'; i++); while (i < size && data[i] == ' ') i++; return (i >= size || data[i] == '\n') ? 2 : 0; } return 0; } static int is_next_headerline(uint8_t *data, size_t size) { size_t i = 0; while (i < size && data[i] != '\n') i++; if (++i >= size) return 0; return is_headerline(data + i, size - i); } /* prefix_quote • returns blockquote prefix length */ static size_t prefix_quote(uint8_t *data, size_t size) { size_t i = 0; if (i < size && data[i] == ' ') i++; if (i < size && data[i] == ' ') i++; if (i < size && data[i] == ' ') i++; if (i < size && data[i] == '>') { if (i + 1 < size && data[i + 1] == ' ') return i + 2; return i + 1; } return 0; } /* prefix_code • returns prefix length for block code*/ static size_t prefix_code(uint8_t *data, size_t size) { if (size > 3 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ') return 4; return 0; } /* prefix_oli • returns ordered list item prefix */ static size_t prefix_oli(uint8_t *data, size_t size) { size_t i = 0; if (i < size && data[i] == ' ') i++; if (i < size && data[i] == ' ') i++; if (i < size && data[i] == ' ') i++; if (i >= size || data[i] < '0' || data[i] > '9') return 0; while (i < size && data[i] >= '0' && data[i] <= '9') i++; if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ') return 0; if (is_next_headerline(data + i, size - i)) return 0; return i + 2; } /* prefix_uli • returns ordered list item prefix */ static size_t prefix_uli(uint8_t *data, size_t size) { size_t i = 0; if (i < size && data[i] == ' ') i++; if (i < size && data[i] == ' ') i++; if (i < size && data[i] == ' ') i++; if (i + 1 >= size || (data[i] != '*' && data[i] != '+' && data[i] != '-') || data[i + 1] != ' ') return 0; if (is_next_headerline(data + i, size - i)) return 0; return i + 2; } /* parse_block • parsing of one block, returning next uint8_t to parse */ static void parse_block(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size); /* parse_blockquote • handles parsing of a blockquote fragment */ static size_t parse_blockquote(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size) { size_t beg, end = 0, pre, work_size = 0; uint8_t *work_data = 0; struct buf *out = 0; out = rndr_newbuf(rndr, BUFFER_BLOCK); beg = 0; while (beg < size) { for (end = beg + 1; end < size && data[end - 1] != '\n'; end++); pre = prefix_quote(data + beg, end - beg); if (pre) beg += pre; /* skipping prefix */ /* empty line followed by non-quote line */ else if (is_empty(data + beg, end - beg) && (end >= size || (prefix_quote(data + end, size - end) == 0 && !is_empty(data + end, size - end)))) break; if (beg < end) { /* copy into the in-place working buffer */ /* bufput(work, data + beg, end - beg); */ if (!work_data) work_data = data + beg; else if (data + beg != work_data + work_size) memmove(work_data + work_size, data + beg, end - beg); work_size += end - beg; } beg = end; } parse_block(out, rndr, work_data, work_size); if (rndr->cb.blockquote) rndr->cb.blockquote(ob, out, rndr->opaque); rndr_popbuf(rndr, BUFFER_BLOCK); return end; } static size_t parse_htmlblock(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int do_render); /* parse_blockquote • handles parsing of a regular paragraph */ static size_t parse_paragraph(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size) { size_t i = 0, end = 0; int level = 0; struct buf work = { data, 0, 0, 0 }; while (i < size) { for (end = i + 1; end < size && data[end - 1] != '\n'; end++) /* empty */; if (is_empty(data + i, size - i)) break; if ((level = is_headerline(data + i, size - i)) != 0) break; if (is_atxheader(rndr, data + i, size - i) || is_hrule(data + i, size - i) || prefix_quote(data + i, size - i)) { end = i; break; } /* * Early termination of a paragraph with the same logic * as Markdown 1.0.0. If this logic is applied, the * Markdown 1.0.3 test suite won't pass cleanly * * :: If the first character in a new line is not a letter, * let's check to see if there's some kind of block starting * here */ if ((rndr->ext_flags & MKDEXT_LAX_SPACING) && !isalnum(data[i])) { if (prefix_oli(data + i, size - i) || prefix_uli(data + i, size - i)) { end = i; break; } /* see if an html block starts here */ if (data[i] == '<' && rndr->cb.blockhtml && parse_htmlblock(ob, rndr, data + i, size - i, 0)) { end = i; break; } /* see if a code fence starts here */ if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 && is_codefence(data + i, size - i, NULL) != 0) { end = i; break; } } i = end; } work.size = i; while (work.size && data[work.size - 1] == '\n') work.size--; if (!level) { struct buf *tmp = rndr_newbuf(rndr, BUFFER_BLOCK); parse_inline(tmp, rndr, work.data, work.size); if (rndr->cb.paragraph) rndr->cb.paragraph(ob, tmp, rndr->opaque); rndr_popbuf(rndr, BUFFER_BLOCK); } else { struct buf *header_work; if (work.size) { size_t beg; i = work.size; work.size -= 1; while (work.size && data[work.size] != '\n') work.size -= 1; beg = work.size + 1; while (work.size && data[work.size - 1] == '\n') work.size -= 1; if (work.size > 0) { struct buf *tmp = rndr_newbuf(rndr, BUFFER_BLOCK); parse_inline(tmp, rndr, work.data, work.size); if (rndr->cb.paragraph) rndr->cb.paragraph(ob, tmp, rndr->opaque); rndr_popbuf(rndr, BUFFER_BLOCK); work.data += beg; work.size = i - beg; } else work.size = i; } header_work = rndr_newbuf(rndr, BUFFER_SPAN); parse_inline(header_work, rndr, work.data, work.size); if (rndr->cb.header) rndr->cb.header(ob, header_work, (int)level, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); } return end; } /* parse_fencedcode • handles parsing of a block-level code fragment */ static size_t parse_fencedcode(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size) { size_t beg, end; struct buf *work = 0; struct buf lang = { 0, 0, 0, 0 }; beg = is_codefence(data, size, &lang); if (beg == 0) return 0; work = rndr_newbuf(rndr, BUFFER_BLOCK); while (beg < size) { size_t fence_end; struct buf fence_trail = { 0, 0, 0, 0 }; fence_end = is_codefence(data + beg, size - beg, &fence_trail); if (fence_end != 0 && fence_trail.size == 0) { beg += fence_end; break; } for (end = beg + 1; end < size && data[end - 1] != '\n'; end++); if (beg < end) { /* verbatim copy to the working buffer, escaping entities */ if (is_empty(data + beg, end - beg)) bufputc(work, '\n'); else bufput(work, data + beg, end - beg); } beg = end; } if (work->size && work->data[work->size - 1] != '\n') bufputc(work, '\n'); if (rndr->cb.blockcode) rndr->cb.blockcode(ob, work, lang.size ? &lang : NULL, rndr->opaque); rndr_popbuf(rndr, BUFFER_BLOCK); return beg; } static size_t parse_blockcode(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size) { size_t beg, end, pre; struct buf *work = 0; work = rndr_newbuf(rndr, BUFFER_BLOCK); beg = 0; while (beg < size) { for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {}; pre = prefix_code(data + beg, end - beg); if (pre) beg += pre; /* skipping prefix */ else if (!is_empty(data + beg, end - beg)) /* non-empty non-prefixed line breaks the pre */ break; if (beg < end) { /* verbatim copy to the working buffer, escaping entities */ if (is_empty(data + beg, end - beg)) bufputc(work, '\n'); else bufput(work, data + beg, end - beg); } beg = end; } while (work->size && work->data[work->size - 1] == '\n') work->size -= 1; bufputc(work, '\n'); if (rndr->cb.blockcode) rndr->cb.blockcode(ob, work, NULL, rndr->opaque); rndr_popbuf(rndr, BUFFER_BLOCK); return beg; } /* parse_listitem • parsing of a single list item */ /* assuming initial prefix is already removed */ static size_t parse_listitem(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int *flags) { struct buf *work = 0, *inter = 0; size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i; int in_empty = 0, has_inside_empty = 0, in_fence = 0; /* keeping track of the first indentation prefix */ while (orgpre < 3 && orgpre < size && data[orgpre] == ' ') orgpre++; beg = prefix_uli(data, size); if (!beg) beg = prefix_oli(data, size); if (!beg) return 0; /* skipping to the beginning of the following line */ end = beg; while (end < size && data[end - 1] != '\n') end++; /* getting working buffers */ work = rndr_newbuf(rndr, BUFFER_SPAN); inter = rndr_newbuf(rndr, BUFFER_SPAN); /* putting the first line into the working buffer */ bufput(work, data + beg, end - beg); beg = end; /* process the following lines */ while (beg < size) { size_t has_next_uli = 0, has_next_oli = 0; end++; while (end < size && data[end - 1] != '\n') end++; /* process an empty line */ if (is_empty(data + beg, end - beg)) { in_empty = 1; beg = end; continue; } /* calculating the indentation */ i = 0; while (i < 4 && beg + i < end && data[beg + i] == ' ') i++; pre = i; if (rndr->ext_flags & MKDEXT_FENCED_CODE) { if (is_codefence(data + beg + i, end - beg - i, NULL) != 0) in_fence = !in_fence; } /* Only check for new list items if we are **not** inside * a fenced code block */ if (!in_fence) { has_next_uli = prefix_uli(data + beg + i, end - beg - i); has_next_oli = prefix_oli(data + beg + i, end - beg - i); } /* checking for ul/ol switch */ if (in_empty && ( ((*flags & MKD_LIST_ORDERED) && has_next_uli) || (!(*flags & MKD_LIST_ORDERED) && has_next_oli))){ *flags |= MKD_LI_END; break; /* the following item must have same list type */ } /* checking for a new item */ if ((has_next_uli && !is_hrule(data + beg + i, end - beg - i)) || has_next_oli) { if (in_empty) has_inside_empty = 1; if (pre == orgpre) /* the following item must have */ break; /* the same indentation */ if (!sublist) sublist = work->size; } /* joining only indented stuff after empty lines; * note that now we only require 1 space of indentation * to continue a list */ else if (in_empty && pre == 0) { *flags |= MKD_LI_END; break; } else if (in_empty) { bufputc(work, '\n'); has_inside_empty = 1; } in_empty = 0; /* adding the line without prefix into the working buffer */ bufput(work, data + beg + i, end - beg - i); beg = end; } /* render of li contents */ if (has_inside_empty) *flags |= MKD_LI_BLOCK; if (*flags & MKD_LI_BLOCK) { /* intermediate render of block li */ if (sublist && sublist < work->size) { parse_block(inter, rndr, work->data, sublist); parse_block(inter, rndr, work->data + sublist, work->size - sublist); } else parse_block(inter, rndr, work->data, work->size); } else { /* intermediate render of inline li */ if (sublist && sublist < work->size) { parse_inline(inter, rndr, work->data, sublist); parse_block(inter, rndr, work->data + sublist, work->size - sublist); } else parse_inline(inter, rndr, work->data, work->size); } /* render of li itself */ if (rndr->cb.listitem) rndr->cb.listitem(ob, inter, *flags, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); rndr_popbuf(rndr, BUFFER_SPAN); return beg; } /* parse_list • parsing ordered or unordered list block */ static size_t parse_list(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int flags) { struct buf *work = 0; size_t i = 0, j; work = rndr_newbuf(rndr, BUFFER_BLOCK); while (i < size) { j = parse_listitem(work, rndr, data + i, size - i, &flags); i += j; if (!j || (flags & MKD_LI_END)) break; } if (rndr->cb.list) rndr->cb.list(ob, work, flags, rndr->opaque); rndr_popbuf(rndr, BUFFER_BLOCK); return i; } /* parse_atxheader • parsing of atx-style headers */ static size_t parse_atxheader(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size) { size_t level = 0; size_t i, end, skip; while (level < size && level < 6 && data[level] == '#') level++; for (i = level; i < size && data[i] == ' '; i++); for (end = i; end < size && data[end] != '\n'; end++); skip = end; while (end && data[end - 1] == '#') end--; while (end && data[end - 1] == ' ') end--; if (end > i) { struct buf *work = rndr_newbuf(rndr, BUFFER_SPAN); parse_inline(work, rndr, data + i, end - i); if (rndr->cb.header) rndr->cb.header(ob, work, (int)level, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); } return skip; } /* htmlblock_end • checking end of HTML block : [ \t]*\n[ \t*]\n */ /* returns the length on match, 0 otherwise */ static size_t htmlblock_end_tag( const char *tag, size_t tag_len, struct sd_markdown *rndr, uint8_t *data, size_t size) { size_t i, w; /* checking if tag is a match */ if (tag_len + 3 >= size || strncasecmp((char *)data + 2, tag, tag_len) != 0 || data[tag_len + 2] != '>') return 0; /* checking white lines */ i = tag_len + 3; w = 0; if (i < size && (w = is_empty(data + i, size - i)) == 0) return 0; /* non-blank after tag */ i += w; w = 0; if (i < size) w = is_empty(data + i, size - i); return i + w; } static size_t htmlblock_end(const char *curtag, struct sd_markdown *rndr, uint8_t *data, size_t size, int start_of_line) { size_t tag_size = strlen(curtag); size_t i = 1, end_tag; int block_lines = 0; while (i < size) { i++; while (i < size && !(data[i - 1] == '<' && data[i] == '/')) { if (data[i] == '\n') block_lines++; i++; } /* If we are only looking for unindented tags, skip the tag * if it doesn't follow a newline. * * The only exception to this is if the tag is still on the * initial line; in that case it still counts as a closing * tag */ if (start_of_line && block_lines > 0 && data[i - 2] != '\n') continue; if (i + 2 + tag_size >= size) break; end_tag = htmlblock_end_tag(curtag, tag_size, rndr, data + i - 1, size - i + 1); if (end_tag) return i + end_tag - 1; } return 0; } /* parse_htmlblock • parsing of inline HTML block */ static size_t parse_htmlblock(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int do_render) { size_t i, j = 0, tag_end; const char *curtag = NULL; struct buf work = { data, 0, 0, 0 }; /* identification of the opening tag */ if (size < 2 || data[0] != '<') return 0; i = 1; while (i < size && data[i] != '>' && data[i] != ' ') i++; if (i < size) curtag = find_block_tag((char *)data + 1, (int)i - 1); /* handling of special cases */ if (!curtag) { /* HTML comment, laxist form */ if (size > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') { i = 5; while (i < size && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>')) i++; i++; if (i < size) j = is_empty(data + i, size - i); if (j) { work.size = i + j; if (do_render && rndr->cb.blockhtml) rndr->cb.blockhtml(ob, &work, rndr->opaque); return work.size; } } /* HR, which is the only self-closing block tag considered */ if (size > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) { i = 3; while (i < size && data[i] != '>') i++; if (i + 1 < size) { i++; j = is_empty(data + i, size - i); if (j) { work.size = i + j; if (do_render && rndr->cb.blockhtml) rndr->cb.blockhtml(ob, &work, rndr->opaque); return work.size; } } } /* no special case recognised */ return 0; } /* looking for an unindented matching closing tag */ /* followed by a blank line */ tag_end = htmlblock_end(curtag, rndr, data, size, 1); /* if not found, trying a second pass looking for indented match */ /* but not if tag is "ins" or "del" (following original Markdown.pl) */ if (!tag_end && strcmp(curtag, "ins") != 0 && strcmp(curtag, "del") != 0) { tag_end = htmlblock_end(curtag, rndr, data, size, 0); } if (!tag_end) return 0; /* the end of the block has been found */ work.size = tag_end; if (do_render && rndr->cb.blockhtml) rndr->cb.blockhtml(ob, &work, rndr->opaque); return tag_end; } static void parse_table_row( struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, size_t columns, int *col_data, int header_flag) { size_t i = 0, col; struct buf *row_work = 0; if (!rndr->cb.table_cell || !rndr->cb.table_row) return; row_work = rndr_newbuf(rndr, BUFFER_SPAN); if (i < size && data[i] == '|') i++; for (col = 0; col < columns && i < size; ++col) { size_t cell_start, cell_end; struct buf *cell_work; cell_work = rndr_newbuf(rndr, BUFFER_SPAN); while (i < size && _isspace(data[i])) i++; cell_start = i; while (i < size && data[i] != '|') i++; cell_end = i - 1; while (cell_end > cell_start && _isspace(data[cell_end])) cell_end--; parse_inline(cell_work, rndr, data + cell_start, 1 + cell_end - cell_start); rndr->cb.table_cell(row_work, cell_work, col_data[col] | header_flag, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); i++; } for (; col < columns; ++col) { struct buf empty_cell = { 0, 0, 0, 0 }; rndr->cb.table_cell(row_work, &empty_cell, col_data[col] | header_flag, rndr->opaque); } rndr->cb.table_row(ob, row_work, rndr->opaque); rndr_popbuf(rndr, BUFFER_SPAN); } static size_t parse_table_header( struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, size_t *columns, int **column_data) { int pipes; size_t i = 0, col, header_end, under_end; pipes = 0; while (i < size && data[i] != '\n') if (data[i++] == '|') pipes++; if (i == size || pipes == 0) return 0; header_end = i; while (header_end > 0 && _isspace(data[header_end - 1])) header_end--; if (data[0] == '|') pipes--; if (header_end && data[header_end - 1] == '|') pipes--; *columns = pipes + 1; *column_data = calloc(*columns, sizeof(int)); /* Parse the header underline */ i++; if (i < size && data[i] == '|') i++; under_end = i; while (under_end < size && data[under_end] != '\n') under_end++; for (col = 0; col < *columns && i < under_end; ++col) { size_t dashes = 0; while (i < under_end && data[i] == ' ') i++; if (data[i] == ':') { i++; (*column_data)[col] |= MKD_TABLE_ALIGN_L; dashes++; } while (i < under_end && data[i] == '-') { i++; dashes++; } if (i < under_end && data[i] == ':') { i++; (*column_data)[col] |= MKD_TABLE_ALIGN_R; dashes++; } while (i < under_end && data[i] == ' ') i++; if (i < under_end && data[i] != '|') break; if (dashes < 3) break; i++; } if (col < *columns) return 0; parse_table_row( ob, rndr, data, header_end, *columns, *column_data, MKD_TABLE_HEADER ); return under_end + 1; } static size_t parse_table( struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size) { size_t i; struct buf *header_work = 0; struct buf *body_work = 0; size_t columns; int *col_data = NULL; header_work = rndr_newbuf(rndr, BUFFER_SPAN); body_work = rndr_newbuf(rndr, BUFFER_BLOCK); i = parse_table_header(header_work, rndr, data, size, &columns, &col_data); if (i > 0) { while (i < size) { size_t row_start; int pipes = 0; row_start = i; while (i < size && data[i] != '\n') if (data[i++] == '|') pipes++; if (pipes == 0 || i == size) { i = row_start; break; } parse_table_row( body_work, rndr, data + row_start, i - row_start, columns, col_data, 0 ); i++; } if (rndr->cb.table) rndr->cb.table(ob, header_work, body_work, rndr->opaque); } free(col_data); rndr_popbuf(rndr, BUFFER_SPAN); rndr_popbuf(rndr, BUFFER_BLOCK); return i; } /* parse_block • parsing of one block, returning next uint8_t to parse */ static void parse_block(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size) { size_t beg, end, i; uint8_t *txt_data; beg = 0; if (rndr->work_bufs[BUFFER_SPAN].size + rndr->work_bufs[BUFFER_BLOCK].size > rndr->max_nesting) return; while (beg < size) { txt_data = data + beg; end = size - beg; if (is_atxheader(rndr, txt_data, end)) beg += parse_atxheader(ob, rndr, txt_data, end); else if (data[beg] == '<' && rndr->cb.blockhtml && (i = parse_htmlblock(ob, rndr, txt_data, end, 1)) != 0) beg += i; else if ((i = is_empty(txt_data, end)) != 0) beg += i; else if (is_hrule(txt_data, end)) { if (rndr->cb.hrule) rndr->cb.hrule(ob, rndr->opaque); while (beg < size && data[beg] != '\n') beg++; beg++; } else if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 && (i = parse_fencedcode(ob, rndr, txt_data, end)) != 0) beg += i; else if ((rndr->ext_flags & MKDEXT_TABLES) != 0 && (i = parse_table(ob, rndr, txt_data, end)) != 0) beg += i; else if (prefix_quote(txt_data, end)) beg += parse_blockquote(ob, rndr, txt_data, end); else if (prefix_code(txt_data, end)) beg += parse_blockcode(ob, rndr, txt_data, end); else if (prefix_uli(txt_data, end)) beg += parse_list(ob, rndr, txt_data, end, 0); else if (prefix_oli(txt_data, end)) beg += parse_list(ob, rndr, txt_data, end, MKD_LIST_ORDERED); else beg += parse_paragraph(ob, rndr, txt_data, end); } } /********************* * REFERENCE PARSING * *********************/ /* is_ref • returns whether a line is a reference or not */ static int is_ref(const uint8_t *data, size_t beg, size_t end, size_t *last, struct link_ref **refs) { /* int n; */ size_t i = 0; size_t id_offset, id_end; size_t link_offset, link_end; size_t title_offset, title_end; size_t line_end; /* up to 3 optional leading spaces */ if (beg + 3 >= end) return 0; if (data[beg] == ' ') { i = 1; if (data[beg + 1] == ' ') { i = 2; if (data[beg + 2] == ' ') { i = 3; if (data[beg + 3] == ' ') return 0; } } } i += beg; /* id part: anything but a newline between brackets */ if (data[i] != '[') return 0; i++; id_offset = i; while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']') i++; if (i >= end || data[i] != ']') return 0; id_end = i; /* spacer: colon (space | tab)* newline? (space | tab)* */ i++; if (i >= end || data[i] != ':') return 0; i++; while (i < end && data[i] == ' ') i++; if (i < end && (data[i] == '\n' || data[i] == '\r')) { i++; if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; } while (i < end && data[i] == ' ') i++; if (i >= end) return 0; /* link: whitespace-free sequence, optionally between angle brackets */ if (data[i] == '<') i++; link_offset = i; while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r') i++; if (data[i - 1] == '>') link_end = i - 1; else link_end = i; /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */ while (i < end && data[i] == ' ') i++; if (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(') return 0; line_end = 0; /* computing end-of-line */ if (i >= end || data[i] == '\r' || data[i] == '\n') line_end = i; if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r') line_end = i + 1; /* optional (space|tab)* spacer after a newline */ if (line_end) { i = line_end + 1; while (i < end && data[i] == ' ') i++; } /* optional title: any non-newline sequence enclosed in '"() alone on its line */ title_offset = title_end = 0; if (i + 1 < end && (data[i] == '\'' || data[i] == '"' || data[i] == '(')) { i++; title_offset = i; /* looking for EOL */ while (i < end && data[i] != '\n' && data[i] != '\r') i++; if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r') title_end = i + 1; else title_end = i; /* stepping back */ i -= 1; while (i > title_offset && data[i] == ' ') i -= 1; if (i > title_offset && (data[i] == '\'' || data[i] == '"' || data[i] == ')')) { line_end = title_end; title_end = i; } } if (!line_end || link_end == link_offset) return 0; /* garbage after the link empty link */ /* a valid ref has been found, filling-in return structures */ if (last) *last = line_end; if (refs) { struct link_ref *ref; ref = add_link_ref(refs, data + id_offset, id_end - id_offset); if (!ref) return 0; ref->link = bufnew(link_end - link_offset); bufput(ref->link, data + link_offset, link_end - link_offset); if (title_end > title_offset) { ref->title = bufnew(title_end - title_offset); bufput(ref->title, data + title_offset, title_end - title_offset); } } return 1; } static void expand_tabs(struct buf *ob, const uint8_t *line, size_t size) { size_t i = 0, tab = 0; while (i < size) { size_t org = i; while (i < size && line[i] != '\t') { i++; tab++; } if (i > org) bufput(ob, line + org, i - org); if (i >= size) break; do { bufputc(ob, ' '); tab++; } while (tab % 4); i++; } } /********************** * EXPORTED FUNCTIONS * **********************/ struct sd_markdown * sd_markdown_new( unsigned int extensions, size_t max_nesting, const struct sd_callbacks *callbacks, void *opaque) { struct sd_markdown *md = NULL; assert(max_nesting > 0 && callbacks); md = malloc(sizeof(struct sd_markdown)); if (!md) return NULL; memcpy(&md->cb, callbacks, sizeof(struct sd_callbacks)); stack_init(&md->work_bufs[BUFFER_BLOCK], 4); stack_init(&md->work_bufs[BUFFER_SPAN], 8); memset(md->active_char, 0x0, 256); if (md->cb.emphasis || md->cb.double_emphasis || md->cb.triple_emphasis) { md->active_char['*'] = MD_CHAR_EMPHASIS; md->active_char['_'] = MD_CHAR_EMPHASIS; if (extensions & MKDEXT_STRIKETHROUGH) md->active_char['~'] = MD_CHAR_EMPHASIS; } if (md->cb.codespan) md->active_char['`'] = MD_CHAR_CODESPAN; if (md->cb.linebreak) md->active_char['\n'] = MD_CHAR_LINEBREAK; if (md->cb.image || md->cb.link) md->active_char['['] = MD_CHAR_LINK; md->active_char['<'] = MD_CHAR_LANGLE; md->active_char['\\'] = MD_CHAR_ESCAPE; md->active_char['&'] = MD_CHAR_ENTITITY; if (extensions & MKDEXT_AUTOLINK) { md->active_char[':'] = MD_CHAR_AUTOLINK_URL; md->active_char['@'] = MD_CHAR_AUTOLINK_EMAIL; md->active_char['w'] = MD_CHAR_AUTOLINK_WWW; } if (extensions & MKDEXT_SUPERSCRIPT) md->active_char['^'] = MD_CHAR_SUPERSCRIPT; /* Extension data */ md->ext_flags = extensions; md->opaque = opaque; md->max_nesting = max_nesting; md->in_link_body = 0; return md; } void sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, struct sd_markdown *md) { #define MARKDOWN_GROW(x) ((x) + ((x) >> 1)) static const char UTF8_BOM[] = {0xEF, 0xBB, 0xBF}; struct buf *text; size_t beg, end; text = bufnew(64); if (!text) return; /* Preallocate enough space for our buffer to avoid expanding while copying */ bufgrow(text, doc_size); /* reset the references table */ memset(&md->refs, 0x0, REF_TABLE_SIZE * sizeof(void *)); /* first pass: looking for references, copying everything else */ beg = 0; /* Skip a possible UTF-8 BOM, even though the Unicode standard * discourages having these in UTF-8 documents */ if (doc_size >= 3 && memcmp(document, UTF8_BOM, 3) == 0) beg += 3; while (beg < doc_size) /* iterating over lines */ if (is_ref(document, beg, doc_size, &end, md->refs)) beg = end; else { /* skipping to the next line */ end = beg; while (end < doc_size && document[end] != '\n' && document[end] != '\r') end++; /* adding the line body if present */ if (end > beg) expand_tabs(text, document + beg, end - beg); while (end < doc_size && (document[end] == '\n' || document[end] == '\r')) { /* add one \n per newline */ if (document[end] == '\n' || (end + 1 < doc_size && document[end + 1] != '\n')) bufputc(text, '\n'); end++; } beg = end; } /* pre-grow the output buffer to minimize allocations */ bufgrow(ob, MARKDOWN_GROW(text->size)); /* second pass: actual rendering */ if (md->cb.doc_header) md->cb.doc_header(ob, md->opaque); if (text->size) { /* adding a final newline if not already present */ if (text->data[text->size - 1] != '\n' && text->data[text->size - 1] != '\r') bufputc(text, '\n'); parse_block(ob, md, text->data, text->size); } if (md->cb.doc_footer) md->cb.doc_footer(ob, md->opaque); /* clean-up */ bufrelease(text); free_link_refs(md->refs); assert(md->work_bufs[BUFFER_SPAN].size == 0); assert(md->work_bufs[BUFFER_BLOCK].size == 0); } void sd_markdown_free(struct sd_markdown *md) { size_t i; for (i = 0; i < (size_t)md->work_bufs[BUFFER_SPAN].asize; ++i) bufrelease(md->work_bufs[BUFFER_SPAN].item[i]); for (i = 0; i < (size_t)md->work_bufs[BUFFER_BLOCK].asize; ++i) bufrelease(md->work_bufs[BUFFER_BLOCK].item[i]); stack_free(&md->work_bufs[BUFFER_SPAN]); stack_free(&md->work_bufs[BUFFER_BLOCK]); free(md); } void sd_version(int *ver_major, int *ver_minor, int *ver_revision) { *ver_major = SUNDOWN_VER_MAJOR; *ver_minor = SUNDOWN_VER_MINOR; *ver_revision = SUNDOWN_VER_REVISION; } /* vim: set filetype=c: */ pumpa-0.9.2/src/sundown/autolink.h0000644000175000017500000000264612653206625015640 0ustar matsmats/* * Copyright (c) 2011, Vicent Marti * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef UPSKIRT_AUTOLINK_H #define UPSKIRT_AUTOLINK_H #include "buffer.h" #ifdef __cplusplus extern "C" { #endif enum { SD_AUTOLINK_SHORT_DOMAINS = (1 << 0), }; int sd_autolink_issafe(const uint8_t *link, size_t link_len); size_t sd_autolink__www(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size, unsigned int flags); size_t sd_autolink__email(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size, unsigned int flags); size_t sd_autolink__url(size_t *rewind_p, struct buf *link, uint8_t *data, size_t offset, size_t size, unsigned int flags); #ifdef __cplusplus } #endif #endif /* vim: set filetype=c: */ pumpa-0.9.2/src/sundown/buffer.c0000644000175000017500000001054612653206625015254 0ustar matsmats/* * Copyright (c) 2008, Natacha Porté * Copyright (c) 2011, Vicent Martí * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define BUFFER_MAX_ALLOC_SIZE (1024 * 1024 * 16) //16mb #include "buffer.h" #include #include #include #include /* MSVC compat */ #if defined(_MSC_VER) # define _buf_vsnprintf _vsnprintf #else # define _buf_vsnprintf vsnprintf #endif int bufprefix(const struct buf *buf, const char *prefix) { size_t i; assert(buf && buf->unit); for (i = 0; i < buf->size; ++i) { if (prefix[i] == 0) return 0; if (buf->data[i] != prefix[i]) return buf->data[i] - prefix[i]; } return 0; } /* bufgrow: increasing the allocated size to the given value */ int bufgrow(struct buf *buf, size_t neosz) { size_t neoasz; void *neodata; assert(buf && buf->unit); if (neosz > BUFFER_MAX_ALLOC_SIZE) return BUF_ENOMEM; if (buf->asize >= neosz) return BUF_OK; neoasz = buf->asize + buf->unit; while (neoasz < neosz) neoasz += buf->unit; neodata = realloc(buf->data, neoasz); if (!neodata) return BUF_ENOMEM; buf->data = neodata; buf->asize = neoasz; return BUF_OK; } /* bufnew: allocation of a new buffer */ struct buf * bufnew(size_t unit) { struct buf *ret; ret = malloc(sizeof (struct buf)); if (ret) { ret->data = 0; ret->size = ret->asize = 0; ret->unit = unit; } return ret; } /* bufnullterm: NULL-termination of the string array */ const char * bufcstr(struct buf *buf) { assert(buf && buf->unit); if (buf->size < buf->asize && buf->data[buf->size] == 0) return (char *)buf->data; if (buf->size + 1 <= buf->asize || bufgrow(buf, buf->size + 1) == 0) { buf->data[buf->size] = 0; return (char *)buf->data; } return NULL; } /* bufprintf: formatted printing to a buffer */ void bufprintf(struct buf *buf, const char *fmt, ...) { va_list ap; int n; assert(buf && buf->unit); if (buf->size >= buf->asize && bufgrow(buf, buf->size + 1) < 0) return; va_start(ap, fmt); n = _buf_vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap); va_end(ap); if (n < 0) { #ifdef _MSC_VER va_start(ap, fmt); n = _vscprintf(fmt, ap); va_end(ap); #else return; #endif } if ((size_t)n >= buf->asize - buf->size) { if (bufgrow(buf, buf->size + n + 1) < 0) return; va_start(ap, fmt); n = _buf_vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap); va_end(ap); } if (n < 0) return; buf->size += n; } /* bufput: appends raw data to a buffer */ void bufput(struct buf *buf, const void *data, size_t len) { assert(buf && buf->unit); if (buf->size + len > buf->asize && bufgrow(buf, buf->size + len) < 0) return; memcpy(buf->data + buf->size, data, len); buf->size += len; } /* bufputs: appends a NUL-terminated string to a buffer */ void bufputs(struct buf *buf, const char *str) { bufput(buf, str, strlen(str)); } /* bufputc: appends a single uint8_t to a buffer */ void bufputc(struct buf *buf, int c) { assert(buf && buf->unit); if (buf->size + 1 > buf->asize && bufgrow(buf, buf->size + 1) < 0) return; buf->data[buf->size] = c; buf->size += 1; } /* bufrelease: decrease the reference count and free the buffer if needed */ void bufrelease(struct buf *buf) { if (!buf) return; free(buf->data); free(buf); } /* bufreset: frees internal data of the buffer */ void bufreset(struct buf *buf) { if (!buf) return; free(buf->data); buf->data = NULL; buf->size = buf->asize = 0; } /* bufslurp: removes a given number of bytes from the head of the array */ void bufslurp(struct buf *buf, size_t len) { assert(buf && buf->unit); if (len >= buf->size) { buf->size = 0; return; } buf->size -= len; memmove(buf->data, buf->data + len, buf->size); } pumpa-0.9.2/src/sundown/html.c0000755000175000017500000003370212653206625014751 0ustar matsmats/* * Copyright (c) 2009, Natacha Porté * Copyright (c) 2011, Vicent Marti * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "markdown.h" #include "html.h" #include #include #include #include #include "houdini.h" #pragma GCC diagnostic ignored "-Wunused-parameter" #define USE_XHTML(opt) (opt->flags & HTML_USE_XHTML) int sdhtml_is_tag(const uint8_t *tag_data, size_t tag_size, const char *tagname) { size_t i; int closed = 0; if (tag_size < 3 || tag_data[0] != '<') return HTML_TAG_NONE; i = 1; if (tag_data[i] == '/') { closed = 1; i++; } for (; i < tag_size; ++i, ++tagname) { if (*tagname == 0) break; if (tag_data[i] != *tagname) return HTML_TAG_NONE; } if (i == tag_size) return HTML_TAG_NONE; if (isspace(tag_data[i]) || tag_data[i] == '>') return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN; return HTML_TAG_NONE; } static inline void escape_html(struct buf *ob, const uint8_t *source, size_t length) { houdini_escape_html0(ob, source, length, 0); } static inline void escape_href(struct buf *ob, const uint8_t *source, size_t length) { houdini_escape_href(ob, source, length); } /******************** * GENERIC RENDERER * ********************/ static int rndr_autolink(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque) { struct html_renderopt *options = opaque; if (!link || !link->size) return 0; if ((options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size) && type != MKDA_EMAIL) return 0; BUFPUTSL(ob, "data, link->size); if (options->link_attributes) { bufputc(ob, '\"'); options->link_attributes(ob, link, opaque); bufputc(ob, '>'); } else { BUFPUTSL(ob, "\">"); } /* * Pretty printing: if we get an email address as * an actual URI, e.g. `mailto:foo@bar.com`, we don't * want to print the `mailto:` prefix */ if (bufprefix(link, "mailto:") == 0) { escape_html(ob, link->data + 7, link->size - 7); } else { escape_html(ob, link->data, link->size); } BUFPUTSL(ob, ""); return 1; } static void rndr_blockcode(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque) { if (ob->size) bufputc(ob, '\n'); if (lang && lang->size) { size_t i, cls; BUFPUTSL(ob, "
size; ++i, ++cls) {
			while (i < lang->size && isspace(lang->data[i]))
				i++;

			if (i < lang->size) {
				size_t org = i;
				while (i < lang->size && !isspace(lang->data[i]))
					i++;

				if (lang->data[org] == '.')
					org++;

				if (cls) bufputc(ob, ' ');
				escape_html(ob, lang->data + org, i - org);
			}
		}

		BUFPUTSL(ob, "\">");
	} else
		BUFPUTSL(ob, "
");

	if (text)
		escape_html(ob, text->data, text->size);

	BUFPUTSL(ob, "
\n"); } static void rndr_blockquote(struct buf *ob, const struct buf *text, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "
\n"); if (text) bufput(ob, text->data, text->size); BUFPUTSL(ob, "
\n"); } static int rndr_codespan(struct buf *ob, const struct buf *text, void *opaque) { BUFPUTSL(ob, ""); if (text) escape_html(ob, text->data, text->size); BUFPUTSL(ob, ""); return 1; } static int rndr_strikethrough(struct buf *ob, const struct buf *text, void *opaque) { if (!text || !text->size) return 0; BUFPUTSL(ob, ""); bufput(ob, text->data, text->size); BUFPUTSL(ob, ""); return 1; } static int rndr_double_emphasis(struct buf *ob, const struct buf *text, void *opaque) { if (!text || !text->size) return 0; BUFPUTSL(ob, ""); bufput(ob, text->data, text->size); BUFPUTSL(ob, ""); return 1; } static int rndr_emphasis(struct buf *ob, const struct buf *text, void *opaque) { if (!text || !text->size) return 0; BUFPUTSL(ob, ""); if (text) bufput(ob, text->data, text->size); BUFPUTSL(ob, ""); return 1; } static int rndr_linebreak(struct buf *ob, void *opaque) { struct html_renderopt *options = opaque; bufputs(ob, USE_XHTML(options) ? "
\n" : "
\n"); return 1; } static void rndr_header(struct buf *ob, const struct buf *text, int level, void *opaque) { struct html_renderopt *options = opaque; if (ob->size) bufputc(ob, '\n'); if (options->flags & HTML_TOC) bufprintf(ob, "", level, options->toc_data.header_count++); else bufprintf(ob, "", level); if (text) bufput(ob, text->data, text->size); bufprintf(ob, "\n", level); } static int rndr_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque) { struct html_renderopt *options = opaque; if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size)) return 0; BUFPUTSL(ob, "size) escape_href(ob, link->data, link->size); if (title && title->size) { BUFPUTSL(ob, "\" title=\""); escape_html(ob, title->data, title->size); } if (options->link_attributes) { bufputc(ob, '\"'); options->link_attributes(ob, link, opaque); bufputc(ob, '>'); } else { BUFPUTSL(ob, "\">"); } if (content && content->size) bufput(ob, content->data, content->size); BUFPUTSL(ob, ""); return 1; } static void rndr_list(struct buf *ob, const struct buf *text, int flags, void *opaque) { if (ob->size) bufputc(ob, '\n'); bufput(ob, flags & MKD_LIST_ORDERED ? "
    \n" : "
      \n", 5); if (text) bufput(ob, text->data, text->size); bufput(ob, flags & MKD_LIST_ORDERED ? "
\n" : "\n", 6); } static void rndr_listitem(struct buf *ob, const struct buf *text, int flags, void *opaque) { BUFPUTSL(ob, "
  • "); if (text) { size_t size = text->size; while (size && text->data[size - 1] == '\n') size--; bufput(ob, text->data, size); } BUFPUTSL(ob, "
  • \n"); } static void rndr_paragraph(struct buf *ob, const struct buf *text, void *opaque) { struct html_renderopt *options = opaque; size_t i = 0; if (ob->size) bufputc(ob, '\n'); if (!text || !text->size) return; while (i < text->size && isspace(text->data[i])) i++; if (i == text->size) return; BUFPUTSL(ob, "

    "); if (options->flags & HTML_HARD_WRAP) { size_t org; while (i < text->size) { org = i; while (i < text->size && text->data[i] != '\n') i++; if (i > org) bufput(ob, text->data + org, i - org); /* * do not insert a line break if this newline * is the last character on the paragraph */ if (i >= text->size - 1) break; rndr_linebreak(ob, opaque); i++; } } else { bufput(ob, &text->data[i], text->size - i); } BUFPUTSL(ob, "

    \n"); } static void rndr_raw_block(struct buf *ob, const struct buf *text, void *opaque) { size_t org, sz; if (!text) return; sz = text->size; while (sz > 0 && text->data[sz - 1] == '\n') sz--; org = 0; while (org < sz && text->data[org] == '\n') org++; if (org >= sz) return; if (ob->size) bufputc(ob, '\n'); bufput(ob, text->data + org, sz - org); bufputc(ob, '\n'); } static int rndr_triple_emphasis(struct buf *ob, const struct buf *text, void *opaque) { if (!text || !text->size) return 0; BUFPUTSL(ob, ""); bufput(ob, text->data, text->size); BUFPUTSL(ob, ""); return 1; } static void rndr_hrule(struct buf *ob, void *opaque) { struct html_renderopt *options = opaque; if (ob->size) bufputc(ob, '\n'); bufputs(ob, USE_XHTML(options) ? "
    \n" : "
    \n"); } static int rndr_image(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque) { struct html_renderopt *options = opaque; if (!link || !link->size) return 0; BUFPUTSL(ob, "data, link->size); BUFPUTSL(ob, "\" alt=\""); if (alt && alt->size) escape_html(ob, alt->data, alt->size); if (title && title->size) { BUFPUTSL(ob, "\" title=\""); escape_html(ob, title->data, title->size); } bufputs(ob, USE_XHTML(options) ? "\"/>" : "\">"); return 1; } static int rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque) { struct html_renderopt *options = opaque; /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES * It doens't see if there are any valid tags, just escape all of them. */ if((options->flags & HTML_ESCAPE) != 0) { escape_html(ob, text->data, text->size); return 1; } if ((options->flags & HTML_SKIP_HTML) != 0) return 1; if ((options->flags & HTML_SKIP_STYLE) != 0 && sdhtml_is_tag(text->data, text->size, "style")) return 1; if ((options->flags & HTML_SKIP_LINKS) != 0 && sdhtml_is_tag(text->data, text->size, "a")) return 1; if ((options->flags & HTML_SKIP_IMAGES) != 0 && sdhtml_is_tag(text->data, text->size, "img")) return 1; bufput(ob, text->data, text->size); return 1; } static void rndr_table(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque) { if (ob->size) bufputc(ob, '\n'); BUFPUTSL(ob, "\n"); if (header) bufput(ob, header->data, header->size); BUFPUTSL(ob, "\n"); if (body) bufput(ob, body->data, body->size); BUFPUTSL(ob, "
    \n"); } static void rndr_tablerow(struct buf *ob, const struct buf *text, void *opaque) { BUFPUTSL(ob, "\n"); if (text) bufput(ob, text->data, text->size); BUFPUTSL(ob, "\n"); } static void rndr_tablecell(struct buf *ob, const struct buf *text, int flags, void *opaque) { if (flags & MKD_TABLE_HEADER) { BUFPUTSL(ob, ""); break; case MKD_TABLE_ALIGN_L: BUFPUTSL(ob, " align=\"left\">"); break; case MKD_TABLE_ALIGN_R: BUFPUTSL(ob, " align=\"right\">"); break; default: BUFPUTSL(ob, ">"); } if (text) bufput(ob, text->data, text->size); if (flags & MKD_TABLE_HEADER) { BUFPUTSL(ob, "\n"); } else { BUFPUTSL(ob, "\n"); } } static int rndr_superscript(struct buf *ob, const struct buf *text, void *opaque) { if (!text || !text->size) return 0; BUFPUTSL(ob, ""); bufput(ob, text->data, text->size); BUFPUTSL(ob, ""); return 1; } static void rndr_normal_text(struct buf *ob, const struct buf *text, void *opaque) { if (text) escape_html(ob, text->data, text->size); } static void toc_header(struct buf *ob, const struct buf *text, int level, void *opaque) { struct html_renderopt *options = opaque; /* set the level offset if this is the first header * we're parsing for the document */ if (options->toc_data.current_level == 0) { options->toc_data.level_offset = level - 1; } level -= options->toc_data.level_offset; if (level > options->toc_data.current_level) { while (level > options->toc_data.current_level) { BUFPUTSL(ob, "
      \n
    • \n"); options->toc_data.current_level++; } } else if (level < options->toc_data.current_level) { BUFPUTSL(ob, "
    • \n"); while (level < options->toc_data.current_level) { BUFPUTSL(ob, "
    \n\n"); options->toc_data.current_level--; } BUFPUTSL(ob,"
  • \n"); } else { BUFPUTSL(ob,"
  • \n
  • \n"); } bufprintf(ob, "", options->toc_data.header_count++); if (text) escape_html(ob, text->data, text->size); BUFPUTSL(ob, "\n"); } static int toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque) { if (content && content->size) bufput(ob, content->data, content->size); return 1; } static void toc_finalize(struct buf *ob, void *opaque) { struct html_renderopt *options = opaque; while (options->toc_data.current_level > 0) { BUFPUTSL(ob, "
  • \n\n"); options->toc_data.current_level--; } } void sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options) { static const struct sd_callbacks cb_default = { NULL, NULL, NULL, toc_header, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, rndr_codespan, rndr_double_emphasis, rndr_emphasis, NULL, NULL, toc_link, NULL, rndr_triple_emphasis, rndr_strikethrough, rndr_superscript, NULL, NULL, NULL, toc_finalize, }; memset(options, 0x0, sizeof(struct html_renderopt)); options->flags = HTML_TOC; memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks)); } void sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options, unsigned int render_flags) { static const struct sd_callbacks cb_default = { rndr_blockcode, rndr_blockquote, rndr_raw_block, rndr_header, rndr_hrule, rndr_list, rndr_listitem, rndr_paragraph, rndr_table, rndr_tablerow, rndr_tablecell, rndr_autolink, rndr_codespan, rndr_double_emphasis, rndr_emphasis, rndr_image, rndr_linebreak, rndr_link, rndr_raw_html, rndr_triple_emphasis, rndr_strikethrough, rndr_superscript, NULL, rndr_normal_text, NULL, NULL, }; /* Prepare the options pointer */ memset(options, 0x0, sizeof(struct html_renderopt)); options->flags = render_flags; /* Prepare the callbacks */ memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks)); if (render_flags & HTML_SKIP_IMAGES) callbacks->image = NULL; if (render_flags & HTML_SKIP_LINKS) { callbacks->link = NULL; callbacks->autolink = NULL; } if (render_flags & HTML_SKIP_HTML || render_flags & HTML_ESCAPE) callbacks->blockhtml = NULL; } pumpa-0.9.2/src/sundown/markdown.h0000644000175000017500000001136512653206625015632 0ustar matsmats/* markdown.h - generic markdown parser */ /* * Copyright (c) 2009, Natacha Porté * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef UPSKIRT_MARKDOWN_H #define UPSKIRT_MARKDOWN_H #include "buffer.h" #include "autolink.h" #ifdef __cplusplus extern "C" { #endif #define SUNDOWN_VERSION "1.16.0" #define SUNDOWN_VER_MAJOR 1 #define SUNDOWN_VER_MINOR 16 #define SUNDOWN_VER_REVISION 0 /******************** * TYPE DEFINITIONS * ********************/ /* mkd_autolink - type of autolink */ enum mkd_autolink { MKDA_NOT_AUTOLINK, /* used internally when it is not an autolink*/ MKDA_NORMAL, /* normal http/http/ftp/mailto/etc link */ MKDA_EMAIL, /* e-mail link without explit mailto: */ }; enum mkd_tableflags { MKD_TABLE_ALIGN_L = 1, MKD_TABLE_ALIGN_R = 2, MKD_TABLE_ALIGN_CENTER = 3, MKD_TABLE_ALIGNMASK = 3, MKD_TABLE_HEADER = 4 }; enum mkd_extensions { MKDEXT_NO_INTRA_EMPHASIS = (1 << 0), MKDEXT_TABLES = (1 << 1), MKDEXT_FENCED_CODE = (1 << 2), MKDEXT_AUTOLINK = (1 << 3), MKDEXT_STRIKETHROUGH = (1 << 4), MKDEXT_SPACE_HEADERS = (1 << 6), MKDEXT_SUPERSCRIPT = (1 << 7), MKDEXT_LAX_SPACING = (1 << 8), }; /* sd_callbacks - functions for rendering parsed data */ struct sd_callbacks { /* block level callbacks - NULL skips the block */ void (*blockcode)(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque); void (*blockquote)(struct buf *ob, const struct buf *text, void *opaque); void (*blockhtml)(struct buf *ob,const struct buf *text, void *opaque); void (*header)(struct buf *ob, const struct buf *text, int level, void *opaque); void (*hrule)(struct buf *ob, void *opaque); void (*list)(struct buf *ob, const struct buf *text, int flags, void *opaque); void (*listitem)(struct buf *ob, const struct buf *text, int flags, void *opaque); void (*paragraph)(struct buf *ob, const struct buf *text, void *opaque); void (*table)(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque); void (*table_row)(struct buf *ob, const struct buf *text, void *opaque); void (*table_cell)(struct buf *ob, const struct buf *text, int flags, void *opaque); /* span level callbacks - NULL or return 0 prints the span verbatim */ int (*autolink)(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque); int (*codespan)(struct buf *ob, const struct buf *text, void *opaque); int (*double_emphasis)(struct buf *ob, const struct buf *text, void *opaque); int (*emphasis)(struct buf *ob, const struct buf *text, void *opaque); int (*image)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque); int (*linebreak)(struct buf *ob, void *opaque); int (*link)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque); int (*raw_html_tag)(struct buf *ob, const struct buf *tag, void *opaque); int (*triple_emphasis)(struct buf *ob, const struct buf *text, void *opaque); int (*strikethrough)(struct buf *ob, const struct buf *text, void *opaque); int (*superscript)(struct buf *ob, const struct buf *text, void *opaque); /* low level callbacks - NULL copies input directly into the output */ void (*entity)(struct buf *ob, const struct buf *entity, void *opaque); void (*normal_text)(struct buf *ob, const struct buf *text, void *opaque); /* header and footer */ void (*doc_header)(struct buf *ob, void *opaque); void (*doc_footer)(struct buf *ob, void *opaque); }; struct sd_markdown; /********* * FLAGS * *********/ /* list/listitem flags */ #define MKD_LIST_ORDERED 1 #define MKD_LI_BLOCK 2 /*
  • containing block data */ /********************** * EXPORTED FUNCTIONS * **********************/ extern struct sd_markdown * sd_markdown_new( unsigned int extensions, size_t max_nesting, const struct sd_callbacks *callbacks, void *opaque); extern void sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, struct sd_markdown *md); extern void sd_markdown_free(struct sd_markdown *md); extern void sd_version(int *major, int *minor, int *revision); #ifdef __cplusplus } #endif #endif /* vim: set filetype=c: */ pumpa-0.9.2/src/sundown/houdini.h0000644000175000017500000000250412653206625015442 0ustar matsmats#ifndef HOUDINI_H__ #define HOUDINI_H__ #include "buffer.h" #ifdef __cplusplus extern "C" { #endif #ifdef HOUDINI_USE_LOCALE # define _isxdigit(c) isxdigit(c) # define _isdigit(c) isdigit(c) #else /* * Helper _isdigit methods -- do not trust the current locale * */ # define _isxdigit(c) (strchr("0123456789ABCDEFabcdef", (c)) != NULL) # define _isdigit(c) ((c) >= '0' && (c) <= '9') #endif extern void houdini_escape_html(struct buf *ob, const uint8_t *src, size_t size); extern void houdini_escape_html0(struct buf *ob, const uint8_t *src, size_t size, int secure); extern void houdini_unescape_html(struct buf *ob, const uint8_t *src, size_t size); extern void houdini_escape_xml(struct buf *ob, const uint8_t *src, size_t size); extern void houdini_escape_uri(struct buf *ob, const uint8_t *src, size_t size); extern void houdini_escape_url(struct buf *ob, const uint8_t *src, size_t size); extern void houdini_escape_href(struct buf *ob, const uint8_t *src, size_t size); extern void houdini_unescape_uri(struct buf *ob, const uint8_t *src, size_t size); extern void houdini_unescape_url(struct buf *ob, const uint8_t *src, size_t size); extern void houdini_escape_js(struct buf *ob, const uint8_t *src, size_t size); extern void houdini_unescape_js(struct buf *ob, const uint8_t *src, size_t size); #ifdef __cplusplus } #endif #endif pumpa-0.9.2/src/sundown/buffer.h0000644000175000017500000000563112653206625015260 0ustar matsmats/* * Copyright (c) 2008, Natacha Porté * Copyright (c) 2011, Vicent Martí * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef BUFFER_H__ #define BUFFER_H__ #include #include #include #ifdef __cplusplus extern "C" { #endif #if defined(_MSC_VER) #define __attribute__(x) #define inline #endif typedef enum { BUF_OK = 0, BUF_ENOMEM = -1, } buferror_t; /* struct buf: character array buffer */ struct buf { uint8_t *data; /* actual character data */ size_t size; /* size of the string */ size_t asize; /* allocated size (0 = volatile buffer) */ size_t unit; /* reallocation unit size (0 = read-only buffer) */ }; /* CONST_BUF: global buffer from a string litteral */ #define BUF_STATIC(string) \ { (uint8_t *)string, sizeof string -1, sizeof string, 0, 0 } /* VOLATILE_BUF: macro for creating a volatile buffer on the stack */ #define BUF_VOLATILE(strname) \ { (uint8_t *)strname, strlen(strname), 0, 0, 0 } /* BUFPUTSL: optimized bufputs of a string litteral */ #define BUFPUTSL(output, literal) \ bufput(output, literal, sizeof literal - 1) /* bufgrow: increasing the allocated size to the given value */ int bufgrow(struct buf *, size_t); /* bufnew: allocation of a new buffer */ struct buf *bufnew(size_t) __attribute__ ((malloc)); /* bufnullterm: NUL-termination of the string array (making a C-string) */ const char *bufcstr(struct buf *); /* bufprefix: compare the beginning of a buffer with a string */ int bufprefix(const struct buf *buf, const char *prefix); /* bufput: appends raw data to a buffer */ void bufput(struct buf *, const void *, size_t); /* bufputs: appends a NUL-terminated string to a buffer */ void bufputs(struct buf *, const char *); /* bufputc: appends a single char to a buffer */ void bufputc(struct buf *, int); /* bufrelease: decrease the reference count and free the buffer if needed */ void bufrelease(struct buf *); /* bufreset: frees internal data of the buffer */ void bufreset(struct buf *); /* bufslurp: removes a given number of bytes from the head of the array */ void bufslurp(struct buf *, size_t); /* bufprintf: formatted printing to a buffer */ void bufprintf(struct buf *, const char *, ...) __attribute__ ((format (printf, 2, 3))); #ifdef __cplusplus } #endif #endif pumpa-0.9.2/src/contextwidget.cpp0000644000175000017500000000444012653206625015532 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "contextwidget.h" #include "pumpa_defines.h" #include "activitywidget.h" #include //------------------------------------------------------------------------------ ContextWidget::ContextWidget(QWidget* parent) : ASWidget(parent), m_numReplies(-1), m_object(NULL) {} //------------------------------------------------------------------------------ void ContextWidget::setObject(QASObject* obj) { clear(); m_asMode = QAS_OBJECT; m_object = obj; connect(m_object, SIGNAL(changed()), this, SLOT(update()), Qt::UniqueConnection); ObjectWidget* ow = new ObjectWidget(m_object, this); ow->disableLessButton(); ObjectWidgetWithSignals::connectSignals(ow, this); connect(ow, SIGNAL(showContext(QASObject*)), this, SIGNAL(showContext(QASObject*))); m_itemLayout->insertWidget(0, ow); m_itemLayout->addStretch(); refreshObject(m_object); refreshObject(m_object->replies()); updateNumReplies(); m_firstTime = false; } //------------------------------------------------------------------------------ void ContextWidget::fetchNewer() { if (m_object) emit request(m_object->urlOrProxy(), m_asMode | QAS_NEWER); } //------------------------------------------------------------------------------ bool ContextWidget::updateNumReplies() { int oldNr = m_numReplies; m_numReplies = m_object->replies() ? m_object->replies()->size() : -1; return oldNr != m_numReplies; } //------------------------------------------------------------------------------ void ContextWidget::update() { if (!isVisible() && updateNumReplies()) emit highlightMe(); } pumpa-0.9.2/src/recipientedit.h0000644000175000017500000000243412653206625015140 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef RECIPIENT_EDIT_H #define RECIPIENT_EDIT_H #include #include #include //------------------------------------------------------------------------------ class RecipientEdit : public QLineEdit { Q_OBJECT public: RecipientEdit(QWidget* parent=0); void setChoices(QStringList choices); signals: void ready(); protected slots: void insertCompletion(QString completion); protected: virtual void keyPressEvent(QKeyEvent* event); QPair wordPosAtCursor(); void complete(); QStringList m_choices; QCompleter* m_completer; }; #endif pumpa-0.9.2/src/fancyhighlighter.cpp0000644000175000017500000000341712653206625016164 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "fancyhighlighter.h" #include //------------------------------------------------------------------------------ FancyHighlighter::FancyHighlighter(QTextDocument* doc, QASpell* checker) : QSyntaxHighlighter(doc), m_checker(checker) {} //------------------------------------------------------------------------------ void FancyHighlighter::highlightBlock(const QString& text) { QTextCharFormat urlHighlightFormat; urlHighlightFormat.setForeground(QBrush(Qt::blue)); #ifdef USE_ASPELL int index; QTextCharFormat spellErrorFormat; spellErrorFormat.setFontUnderline(true); spellErrorFormat.setUnderlineColor(Qt::red); spellErrorFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); QRegExp rxa("(^|\\s)(\\w[\\w']*\\w)"); index = text.indexOf(rxa); while (index >= 0) { int length = rxa.matchedLength(); int offset = rxa.cap(1).count(); int s = index+offset; int l = length-offset; if (!m_checker->checkWord(rxa.cap(2))) setFormat(s, l, spellErrorFormat); index = text.indexOf(rxa, index + length); } #endif // USE_ASPELL } pumpa-0.9.2/src/pumpasettingsdialog.cpp0000644000175000017500000001473512653206625016735 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "pumpasettingsdialog.h" #include "util.h" #include "pumpa_defines.h" //------------------------------------------------------------------------------ PumpaSettingsDialog::PumpaSettingsDialog(PumpaSettings* settings, QWidget* parent): QDialog(parent), s(settings) { m_layout = new QVBoxLayout; // Account QGroupBox* accountGroupBox = new QGroupBox(tr("Account")); QVBoxLayout* accountLayout = new QVBoxLayout; m_currentAccountLabel = new QLabel(tr("Not logged in currently."), this); accountLayout->addWidget(m_currentAccountLabel); m_authButton = new QPushButton(tr("Change account"), this); connect(m_authButton, SIGNAL(clicked()), this, SLOT(onAuthButtonClicked())); accountLayout->addWidget(m_authButton); QLabel* acctInfoLabel = new QLabel(tr("Clicking \"Change account\" will run the " "authentication setup again for a new pump.io " "account. This will remove the current login " "credentials since Pumpa only supports one " "account at a time.")); acctInfoLabel->setWordWrap(true); accountLayout->addWidget(acctInfoLabel); accountGroupBox->setLayout(accountLayout); // User interface QGroupBox* uiGroupBox = new QGroupBox(tr("Interface")); QFormLayout* uiLayout = new QFormLayout; m_updateTimeSpinBox = new QSpinBox(this); m_updateTimeSpinBox->setMinimum(1); m_updateTimeSpinBox->setMaximum(30); uiLayout->addRow(tr("Update interval (in minutes):"), m_updateTimeSpinBox); QStringList addressItems; addressItems << "" << tr("Public") << tr("Followers"); m_defaultToComboBox = new QComboBox(this); m_defaultToComboBox->addItems(addressItems); uiLayout->addRow(tr("Default \"To\":"), m_defaultToComboBox); m_defaultCcComboBox = new QComboBox(this); m_defaultCcComboBox->addItems(addressItems); uiLayout->addRow(tr("Default \"CC\":"), m_defaultCcComboBox); m_useIconCheckBox = new QCheckBox(tr("Use icon in system tray"), this); uiLayout->addRow(m_useIconCheckBox); m_showCharCountCheckBox = new QCheckBox(tr("Show message character count"), this); uiLayout->addRow(m_showCharCountCheckBox); uiGroupBox->setLayout(uiLayout); // Notifications QGroupBox* notifyGroupBox = new QGroupBox(tr("Notifications")); QFormLayout* notifyLayout = new QFormLayout; QStringList timelineList; timelineList << tr("Never") << tr("Direct only") << tr("Direct or mention") << tr("Direct, mention or inbox") << tr("Anything"); m_highlightComboBox = new QComboBox(this); m_highlightComboBox->addItems(timelineList); notifyLayout->addRow(tr("Highlight tray icon on:"), m_highlightComboBox); m_popupComboBox = new QComboBox(this); m_popupComboBox->addItems(timelineList); notifyLayout->addRow(tr("Popup notification on:"), m_popupComboBox); notifyGroupBox->setLayout(notifyLayout); m_buttonBox = new QDialogButtonBox(this); m_buttonBox->setOrientation(Qt::Horizontal); m_buttonBox->setStandardButtons(QDialogButtonBox::Ok); connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(onOKClicked())); connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); // Dialog layout m_layout->addWidget(uiGroupBox); m_layout->addWidget(notifyGroupBox); m_layout->addWidget(accountGroupBox); m_layout->addWidget(m_buttonBox); setLayout(m_layout); } //------------------------------------------------------------------------------ void PumpaSettingsDialog::onAuthButtonClicked() { accept(); emit newAccount(); } //------------------------------------------------------------------------------ void PumpaSettingsDialog::setVisible(bool visible) { if (visible) updateUI(); QDialog::setVisible(visible); } //------------------------------------------------------------------------------ int PumpaSettingsDialog::comboIndexConverter(int ci, bool backwards) { static QList comboToFeeds; if (comboToFeeds.isEmpty()) { comboToFeeds << 0 << FEED_DIRECT << (FEED_DIRECT | FEED_MENTIONS) << (FEED_DIRECT | FEED_MENTIONS | FEED_INBOX) << (FEED_DIRECT | FEED_MENTIONS | FEED_INBOX | FEED_MEANWHILE); } if (backwards) { int ret = comboToFeeds.indexOf(ci); return ret == -1 ? 0 : ret; } return ci < comboToFeeds.size() ? comboToFeeds[ci] : 0; } //------------------------------------------------------------------------------ void PumpaSettingsDialog::updateUI() { QString accountId = siteUrlToAccountId(s->userName(), s->siteUrl()); m_currentAccountLabel->setText(QString(tr("Currently logged in as %1.")). arg(accountId)); m_updateTimeSpinBox->setValue(s->reloadTime()); m_useIconCheckBox->setChecked(s->useTrayIcon()); m_showCharCountCheckBox->setChecked(s->showCharCount()); m_highlightComboBox-> setCurrentIndex(feedIntToComboIndex(s->highlightFeeds())); m_popupComboBox-> setCurrentIndex(feedIntToComboIndex(s->popupFeeds())); m_defaultToComboBox->setCurrentIndex(s->defaultToAddress()); m_defaultCcComboBox->setCurrentIndex(s->defaultCcAddress()); } //------------------------------------------------------------------------------ void PumpaSettingsDialog::onOKClicked() { s->reloadTime(m_updateTimeSpinBox->value()); s->useTrayIcon(m_useIconCheckBox->isChecked()); s->showCharCount(m_showCharCountCheckBox->isChecked()); s->highlightFeeds(comboIndexToFeedInt(m_highlightComboBox->currentIndex())); s->popupFeeds(comboIndexToFeedInt(m_popupComboBox->currentIndex())); s->defaultToAddress(m_defaultToComboBox->currentIndex()); s->defaultCcAddress(m_defaultCcComboBox->currentIndex()); emit accepted(); } pumpa-0.9.2/src/collectionwidget.h0000644000175000017500000000325312653206625015647 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _COLLECTIONWIDGET_H_ #define _COLLECTIONWIDGET_H_ #include "qactivitystreams.h" #include "activitywidget.h" #include "aswidget.h" //------------------------------------------------------------------------------ class CollectionWidget : public ASWidget { Q_OBJECT public: CollectionWidget(QWidget* parent, int widgetLimit=-1, int purgeWait=10); virtual bool hasObject(QASAbstractObject* obj); protected slots: virtual void update(); void onLoadOlderClicked(); protected: void updateLoadOlderButton(bool wait=false); virtual QASAbstractObjectList* initList(QString endpoint, QObject* parent); virtual ObjectWidgetWithSignals* createWidget(QASAbstractObject* aObj); virtual void changeWidgetObject(ObjectWidgetWithSignals*, QASAbstractObject*); virtual bool countAsNew(QASAbstractObject* aObj); virtual void clear(); bool isFullObject(QASActivity*); private: QPushButton* m_loadOlderButton; QSet m_objects_shown; }; #endif /* _COLLECTIONWIDGET_H_ */ pumpa-0.9.2/src/qactivitystreams.cpp0000644000175000017500000000227612653206625016263 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "qactivitystreams.h" #include "pumpa_defines.h" #include "json.h" #include #include #include //------------------------------------------------------------------------------ void resetActivityStreams() { QASActor::clearCache(); QASObject::clearCache(); QASActivity::clearCache(); QASObjectList::clearCache(); QASActorList::clearCache(); QASCollection::clearCache(); } //------------------------------------------------------------------------------ pumpa-0.9.2/src/editprofiledialog.h0000644000175000017500000000317712653206625016003 0ustar matsmats/* Copyright 2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _EDITPROFILEDIALOG_H_ #define _EDITPROFILEDIALOG_H_ #include #include #include #include #include #include #include #include "qasactor.h" #include "texttoolbutton.h" class EditProfileDialog : public QDialog { Q_OBJECT public: EditProfileDialog(QWidget* parent=0); void setProfile(QASActor* profile); signals: void profileEdited(QASActor*, QString); private slots: void onOKClicked(); void updateImage(); void onChangeImage(); private: QString m_imageFileName; QLabel* m_imageLabel; TextToolButton* m_changeImageButton; QHBoxLayout* m_imageLayout; QLabel* m_realNameLabel; QLineEdit* m_realNameEdit; QLabel* m_hometownLabel; QLineEdit* m_hometownEdit; QLabel* m_bioLabel; QTextEdit* m_bioEdit; QDialogButtonBox* m_buttonBox; QVBoxLayout* m_layout; QASActor* m_profile; }; #endif /* _EDITPROFILEDIALOG_H_ */ pumpa-0.9.2/src/actorwidget.cpp0000644000175000017500000001176212653206625015163 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "actorwidget.h" #include "filedownloader.h" #include #include //------------------------------------------------------------------------------ ActorWidget::ActorWidget(QASActor* a, QWidget* parent, bool small) : QToolButton(parent), m_actor(a) { #ifdef DEBUG_WIDGETS qDebug() << "Creating ActorWidget" << (m_actor ? m_actor->id() : "NULL"); #endif int max_size = small ? 32 : 64; setStyleSheet("QToolButton::menu-indicator { " "subcontrol-origin: padding; " "subcontrol-position: bottom right; " "}"); setIconSize(QSize(max_size, max_size)); setFocusPolicy(Qt::NoFocus); setPopupMode(QToolButton::InstantPopup); setAutoRaise(true); m_menu = new QMenu(this); m_menuTitleAction = new QAction(this); connect(m_menuTitleAction, SIGNAL(triggered()), this, SLOT(onMenuTitle())); m_followAction = new QAction(this); connect(m_followAction, SIGNAL(triggered()), this, SLOT(onFollowAuthor())); m_hideAuthorAction = new QAction(this); connect(m_hideAuthorAction, SIGNAL(triggered()), this, SLOT(onHideAuthor())); createMenu(); onImageChanged(); } //------------------------------------------------------------------------------ ActorWidget::~ActorWidget() { #ifdef DEBUG_WIDGETS qDebug() << "Deleting ActorWidget" << m_actor->id(); #endif } //------------------------------------------------------------------------------ void ActorWidget::setActor(QASActor* a) { if (m_actor == a) return; m_actor = a; onImageChanged(); createMenu(); } //------------------------------------------------------------------------------ void ActorWidget::onImageChanged() { m_url = m_actor ? m_actor->imageUrl() : ""; updatePixmap(); } //------------------------------------------------------------------------------ void ActorWidget::updatePixmap() { FileDownloadManager* fdm = FileDownloadManager::getManager(); if (fdm->hasFile(m_url)) { setIcon(QIcon(fdm->pixmap(m_url, ":/images/image_broken.png"))); } else { if (m_url.isEmpty()) { setIcon(QIcon(":/images/default.png")); } else { setIcon(QIcon(":/images/image_downloading.png")); FileDownloader* fd = fdm->download(m_url); connect(fd, SIGNAL(fileReady()), this, SLOT(updatePixmap()), Qt::UniqueConnection); } } } //------------------------------------------------------------------------------ void ActorWidget::createMenu() { if (!m_actor) { setMenu(NULL); return; } bool isYou = m_actor->isYou(); m_menuTitleAction->setText(m_actor->displayNameOrWebFinger()); m_menu->clear(); m_menu->addAction(m_menuTitleAction); m_menu->addSeparator(); if (!isYou) m_menu->addAction(m_followAction); m_menu->addAction(m_hideAuthorAction); updateMenu(); setMenu(m_menu); } //------------------------------------------------------------------------------ void ActorWidget::updateMenu() { if (!m_actor) return; m_followAction->setText(m_actor->followed() ? tr("stop following") : tr("follow")); m_hideAuthorAction->setText(m_actor->isHidden() ? tr("stop minimising posts") : tr("auto-minimise posts")); } //------------------------------------------------------------------------------ void ActorWidget::onFollowAuthor() { bool doFollow = !m_actor->followed(); if (!doFollow) { QString msg = QString(tr("Are you sure you want to stop following %1?")). arg(m_actor->displayNameOrWebFinger()); int ret = QMessageBox::warning(this, CLIENT_FANCY_NAME, msg, QMessageBox::Cancel | QMessageBox::Yes, QMessageBox::Cancel); if (ret != QMessageBox::Yes) return; } updateMenu(); if (!m_actor->isYou()) emit follow(m_actor->id(), doFollow); } //------------------------------------------------------------------------------ void ActorWidget::onHideAuthor() { bool doHide = !m_actor->isHidden(); m_actor->setHidden(doHide); updateMenu(); if (doHide) emit lessClicked(); else emit moreClicked(); } //------------------------------------------------------------------------------ void ActorWidget::onMenuTitle() { if (m_actor && !m_actor->url().isEmpty()) QDesktopServices::openUrl(m_actor->url()); } pumpa-0.9.2/src/shortobjectwidget.cpp0000644000175000017500000000733012653206625016375 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "shortobjectwidget.h" #include "util.h" #include //------------------------------------------------------------------------------ ShortObjectWidget::ShortObjectWidget(QASObject* obj, QWidget* parent) : QFrame(parent), m_object(NULL), m_actor(NULL) { #ifdef DEBUG_WIDGETS qDebug() << "Creating ShortObjectWidget"; #endif m_textLabel = new RichTextLabel(this, true); m_actorWidget = new ActorWidget(NULL, this, true); connect(m_actorWidget, SIGNAL(follow(QString, bool)), this, SIGNAL(follow(QString, bool))); connect(m_actorWidget, SIGNAL(moreClicked()), this, SIGNAL(moreClicked())); m_moreButton = new TextToolButton("+", this); connect(m_moreButton, SIGNAL(clicked()), this, SIGNAL(moreClicked())); QHBoxLayout* acrossLayout = new QHBoxLayout; // acrossLayout->setSpacing(10); acrossLayout->setContentsMargins(0, 0, 0, 0); acrossLayout->addWidget(m_actorWidget, 0, Qt::AlignVCenter); acrossLayout->addWidget(m_textLabel, 0, Qt::AlignVCenter); acrossLayout->addWidget(m_moreButton, 0, Qt::AlignVCenter); changeObject(obj); setLayout(acrossLayout); } //------------------------------------------------------------------------------ ShortObjectWidget::~ShortObjectWidget() { #ifdef DEBUG_WIDGETS qDebug() << "Deleting ShortObjectWidget" << m_object->id(); #endif } //------------------------------------------------------------------------------ void ShortObjectWidget::changeObject(QASAbstractObject* obj) { if (m_object != NULL) disconnect(m_object, SIGNAL(changed()), this, SLOT(onChanged())); m_object = qobject_cast(obj); if (!m_object) return; connect(m_object, SIGNAL(changed()), this, SLOT(onChanged())); if (m_actor != NULL) disconnect(m_actor, SIGNAL(changed()), m_actorWidget, SLOT(updateMenu())); QASActor* m_actor = m_object->asActor(); if (!m_actor) { m_actor = m_object->author(); if (m_actor) connect(m_actor, SIGNAL(changed()), m_actorWidget, SLOT(updateMenu())); } m_actorWidget->setActor(m_actor); static QSet expandableTypes; if (expandableTypes.isEmpty()) expandableTypes << "person" << "note" << "comment" << "image" << "video" << "audio"; m_moreButton->setVisible(expandableTypes.contains(m_object->type()) && !m_object->isDeleted()); updateText(); } //------------------------------------------------------------------------------ void ShortObjectWidget::updateText() { if (!m_object) return; QString content = m_object->excerpt(); QString text; QASActor* author = m_object->author(); if (author && !author->displayName().isEmpty()) text = author->displayName(); if (!content.isEmpty()) text += (text.isEmpty() ? "" : ": ") + content; m_textLabel->setText(text); } //------------------------------------------------------------------------------ void ShortObjectWidget::onChanged() { updateText(); m_actorWidget->updateMenu(); } //------------------------------------------------------------------------------ pumpa-0.9.2/src/qasactivity.h0000644000175000017500000000472712653206625014660 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _QASACTIVITY_H_ #define _QASACTIVITY_H_ #include "qasabstractobject.h" #include "qasobject.h" #include "qasactor.h" #include "qasobjectlist.h" //------------------------------------------------------------------------------ class QASActivity : public QASAbstractObject { Q_OBJECT QASActivity(QString id, QObject* parent); public: static void clearCache(); static QASActivity* getActivity(QVariantMap json, QObject* parent); void update(QVariantMap json); virtual QString apiLink() const { return id(); } QString id() const { return m_id; } QString verb() const { return m_verb; } QString content() const { return m_content; } QString generatorName() const { return m_generatorName; } bool skipNotify() const; qint64 sortInt() const { return sortIntByDateTime(m_updated); } QASObject* object() const { return m_object; } QASActor* actor() const { return m_actor; } QDateTime published() const { return m_published; } QString url() const { return m_url; } bool hasTo() const; bool hasCc() const; QASObjectList* to() const { return m_to; } QASObjectList* cc() const { return m_cc; } static bool isLikeVerb(QString verb) { return (verb == "favorite" || verb == "like" || verb == "unfavorite" || verb == "unlike"); } static bool isShareVerb(QString verb) { return (verb == "share"); } virtual bool isDeleted() const { return m_verb == "post" && m_object && m_object->isDeleted(); } private: QString m_id; QString m_url; QString m_content; QString m_verb; QString m_generatorName; QDateTime m_published; QDateTime m_updated; QASObject* m_object; QASActor* m_actor; QASObjectList* m_to; QASObjectList* m_cc; static QMap s_activities; }; #endif /* _QASACTIVITY_H_ */ pumpa-0.9.2/src/objectwidgetwithsignals.cpp0000644000175000017500000000647212653206625017600 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "objectwidgetwithsignals.h" #include //------------------------------------------------------------------------------ ObjectWidgetWithSignals::ObjectWidgetWithSignals(QWidget* parent) : QFrame(parent) {} //------------------------------------------------------------------------------ void ObjectWidgetWithSignals::connectSignals(ObjectWidgetWithSignals* ow, QWidget* w) { connect(ow, SIGNAL(linkHovered(const QString&)), w, SIGNAL(linkHovered(const QString&))); connect(ow, SIGNAL(like(QASObject*)), w, SIGNAL(like(QASObject*))); connect(ow, SIGNAL(share(QASObject*)), w, SIGNAL(share(QASObject*))); connect(ow, SIGNAL(newReply(QASObject*, QASObjectList*, QASObjectList*)), w, SIGNAL(newReply(QASObject*, QASObjectList*, QASObjectList*))); connect(ow, SIGNAL(follow(QString, bool)), w, SIGNAL(follow(QString, bool))); connect(ow, SIGNAL(deleteObject(QASObject*)), w, SIGNAL(deleteObject(QASObject*))); connect(ow, SIGNAL(editObject(QASObject*)), w, SIGNAL(editObject(QASObject*))); connect(ow, SIGNAL(request(QString, int)), w, SIGNAL(request(QString, int))); } //------------------------------------------------------------------------------ void ObjectWidgetWithSignals::disconnectSignals(ObjectWidgetWithSignals* ow, QWidget* w) { disconnect(ow, SIGNAL(linkHovered(const QString&)), w, SIGNAL(linkHovered(const QString&))); disconnect(ow, SIGNAL(like(QASObject*)), w, SIGNAL(like(QASObject*))); disconnect(ow, SIGNAL(share(QASObject*)), w, SIGNAL(share(QASObject*))); disconnect(ow, SIGNAL(newReply(QASObject*, QASObjectList*, QASObjectList*)), w, SIGNAL(newReply(QASObject*, QASObjectList*, QASObjectList*))); disconnect(ow, SIGNAL(follow(QString, bool)), w, SIGNAL(follow(QString, bool))); disconnect(ow, SIGNAL(deleteObject(QASObject*)), w, SIGNAL(deleteObject(QASObject*))); disconnect(ow, SIGNAL(editObject(QASObject*)), w, SIGNAL(editObject(QASObject*))); disconnect(ow, SIGNAL(request(QString, int)), w, SIGNAL(request(QString, int))); } //------------------------------------------------------------------------------ void ObjectWidgetWithSignals::refreshObject(QASAbstractObject* obj) { if (!obj) return; QDateTime now = QDateTime::currentDateTime(); QDateTime lr = obj->lastRefreshed(); if (lr.isNull() || lr.secsTo(now) > 10) { obj->lastRefreshed(now); emit request(obj->apiLink(), obj->asType()); } } pumpa-0.9.2/src/filedownloader.h0000644000175000017500000000514612653206625015311 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef FILEDOWNLOADER_H #define FILEDOWNLOADER_H #include #include #include #include #include "QtKOAuth" //------------------------------------------------------------------------------ class FileDownloader; // forward declaration class FileDownloadManager : public QObject { Q_OBJECT FileDownloadManager(QObject*); public: static FileDownloadManager* getManager(QObject* = 0); bool hasFile(QString url); QString fileName(QString url); QPixmap pixmap(QString url, QString brokenImage = ""); QMovie* movie(QString url); bool supportsAnimation(QString url); FileDownloader* download(QString url); void dumpStats(); private slots: void onSslErrors(QNetworkReply*, QList); void onAuthorizedRequestReady(QByteArray response, int id); void onFileReady(QString = ""); private: void executeAuthorizedRequest(KQOAuthRequest*, FileDownloader*); QString urlToPath(QString url); QNetworkAccessManager* m_nam; KQOAuthManager* m_oam; QMap m_inProgress; QMap m_urlMap; QString m_cacheDir; int m_nextRequestId; typedef QPair requestData_t; QMap m_requestMap; static FileDownloadManager* s_instance; friend class FileDownloader; }; //------------------------------------------------------------------------------ class FileDownloader : public QObject { Q_OBJECT public: FileDownloader(QString url, FileDownloadManager* fdm); QString url() const { return m_url; } signals: void networkError(QString); void fileReady(); private slots: void replyFinished(); private: void requestReady(QByteArray response, KQOAuthRequest* oar); static void resizeImage(QPixmap pix, QString fn); QString m_url; KQOAuthRequest* m_oar; int m_redirs; FileDownloadManager* m_fdm; friend class FileDownloadManager; }; #endif pumpa-0.9.2/src/qaspell.cpp0000644000175000017500000000607512653206625014311 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "qaspell.h" #ifdef USE_ASPELL //------------------------------------------------------------------------------ QString QASpell::s_locale = "en_US"; //------------------------------------------------------------------------------ QASpell::QASpell(QObject* parent) : QObject(parent) { ok = false; spell_config = new_aspell_config(); aspell_config_replace(spell_config, "lang", s_locale.toLocal8Bit().constData()); aspell_config_replace(spell_config, "encoding", "ucs-2"); AspellCanHaveError* possible_err = new_aspell_speller(spell_config); spell_checker = NULL; if (aspell_error_number(possible_err) != 0) { qDebug() << aspell_error_message(possible_err); return; } spell_checker = to_aspell_speller(possible_err); ok = true; } //------------------------------------------------------------------------------ QASpell::~QASpell() { if (spell_checker != NULL) delete_aspell_speller(spell_checker); delete_aspell_config(spell_config); } //------------------------------------------------------------------------------ void QASpell::setLocale(QString locale) { if (!locale.isEmpty()) s_locale = locale; } //------------------------------------------------------------------------------ bool QASpell::checksOK() const { if (!ok) return false; if (spell_checker == NULL) { qDebug() << "aspell was not initialised properly!"; return false; } return true; } //------------------------------------------------------------------------------ bool QASpell::checkWord(const QString& word) const { if (!checksOK()) return true; int correct = aspell_speller_check(spell_checker, reinterpret_cast(word.utf16()), -1); return correct; } //------------------------------------------------------------------------------ QStringList QASpell::suggestions(const QString& word) const { QStringList list; if (!checksOK()) return list; const AspellWordList* wl = aspell_speller_suggest(spell_checker, reinterpret_cast(word.utf16()), -1); AspellStringEnumeration* w = aspell_word_list_elements(wl); const char* cw; while ((cw = aspell_string_enumeration_next(w)) != NULL) { list << QString::fromUtf16(reinterpret_cast(cw)); } delete_aspell_string_enumeration(w); return list; } #endif // USE_ASPELL pumpa-0.9.2/src/fullobjectwidget.h0000644000175000017500000000750612653206625015652 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _FULLOBJECTWIDGET_H_ #define _FULLOBJECTWIDGET_H_ #include #include #include #include #include #include #include "objectwidgetwithsignals.h" #include "qactivitystreams.h" #include "texttoolbutton.h" #include "richtextlabel.h" #include "actorwidget.h" #include "imagelabel.h" //------------------------------------------------------------------------------ class FullObjectWidget : public ObjectWidgetWithSignals { Q_OBJECT public: FullObjectWidget(QASObject* obj, QWidget* parent = 0, bool childWidget=false); virtual ~FullObjectWidget(); virtual void changeObject(QASAbstractObject* obj); QASObject* object() const { return m_object; } virtual QASAbstractObject* asObject() const { return object(); } virtual void refreshTimeLabels(); void disableLessButton(); void updateMenu() { if (m_actorWidget) m_actorWidget->updateMenu(); updateFollowAuthorButton(); } void setActivity(QASActivity* a) { m_activity = a; } signals: void lessClicked(); private slots: void onChanged(); void updateImage(); void imageClicked(); void onHasMoreClicked(); void favourite(); void onRepeatClicked(); void reply(); void onFollow(); void onFollowAuthor(); void updateFollowAuthorButton(bool wait = false); void updateDeleteEditButtons(); void onDeleteClicked(); void onEditClicked(); #ifdef DEBUG_BUTTONS void onLoadRepliesClicked(); void onDumpJsonClicked(); #endif private: bool isCommentable() const; QString typeName() const; QString textExcerpt() const; bool hasValidIrtObject(); void setText(QString text); void updateInfoText(); void updateLikes(); void updateShares(); void updateTitle(); QString recipientsToString(QASObjectList* rec); QString processText(QString old_text, bool getImages=false); void addHasMoreButton(QASObjectList* ol, int li); void updateFavourButton(bool wait = false); void updateShareButton(bool wait = false); void updateFollowButton(bool wait = false); bool isFollowable(QASObject* obj) const; void addObjectList(QASObjectList* ol); void clearObjectList(); QString m_imageUrl; QString m_localFile; RichTextLabel* m_textLabel; ImageLabel* m_imageLabel; ActorWidget* m_actorWidget; RichTextLabel* m_streamLabel; RichTextLabel* m_infoLabel; RichTextLabel* m_likesLabel; RichTextLabel* m_sharesLabel; QLabel* m_titleLabel; QPushButton* m_hasMoreButton; TextToolButton* m_favourButton; TextToolButton* m_shareButton; TextToolButton* m_commentButton; TextToolButton* m_followButton; TextToolButton* m_followAuthorButton; TextToolButton* m_deleteButton; TextToolButton* m_editButton; TextToolButton* m_lessButton; #ifdef DEBUG_BUTTONS TextToolButton* m_loadRepliesButton; TextToolButton* m_dumpJsonButton; #endif QVBoxLayout* m_contentLayout; QHBoxLayout* m_buttonLayout; QVBoxLayout* m_commentsLayout; QASObject* m_object; QASActor* m_actor; QASActor* m_author; QList m_repliesList; QSet m_repliesMap; QASActivity* m_activity; bool m_childWidget; bool m_commentable; }; #endif /* _FULLOBJECTWIDGET_H_ */ pumpa-0.9.2/src/objectwidgetwithsignals.h0000644000175000017500000000325312653206625017237 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _OBJECTWIDGETWITHSIGNALS_H_ #define _OBJECTWIDGETWITHSIGNALS_H_ #include #include "qactivitystreams.h" //------------------------------------------------------------------------------ class ObjectWidgetWithSignals : public QFrame { Q_OBJECT public: ObjectWidgetWithSignals(QWidget* parent = 0); virtual void changeObject(QASAbstractObject* obj) = 0; virtual QASAbstractObject* asObject() const = 0; static void connectSignals(ObjectWidgetWithSignals* ow, QWidget* w); static void disconnectSignals(ObjectWidgetWithSignals* ow, QWidget* w); virtual void refreshTimeLabels() = 0; signals: void linkHovered(const QString&); void like(QASObject*); void share(QASObject*); void newReply(QASObject*, QASObjectList*, QASObjectList*); void follow(QString, bool); void deleteObject(QASObject*); void editObject(QASObject*); void request(QString, int); protected: void refreshObject(QASAbstractObject* obj); }; #endif /* _OBJECTWIDGETWITHSIGNALS_H_ */ pumpa-0.9.2/src/messagerecipients.cpp0000644000175000017500000000561012653206625016354 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "messagerecipients.h" #include //------------------------------------------------------------------------------ MessageRecipients::MessageRecipients(QWidget* parent) : QWidget(parent) { m_layout = new QGridLayout; m_layout->setColumnStretch(0, 10); m_layout->setColumnStretch(1, 1); m_layout->setContentsMargins(0, 0, 0, 0); m_buttonMapper = new QSignalMapper(this); connect(m_buttonMapper, SIGNAL(mapped(QObject*)), this, SLOT(onRemoveClicked(QObject*))); setLayout(m_layout); } //------------------------------------------------------------------------------ void MessageRecipients::addRecipient(QASObject* obj) { if (m_widgets.contains(obj)) return; QString text("" + obj->displayName() + ""); QASActor* actor = obj->asActor(); if (actor) text += " (" + actor->webFinger() + ")"; QLabel* label = new QLabel(text); QToolButton* button = new QToolButton; button->setText(tr("-")); connect(button, SIGNAL(clicked()), m_buttonMapper, SLOT(map())); m_buttonMapper->setMapping(button, obj); m_list.append(obj); int row = m_layout->rowCount(); m_layout->addWidget(label, row, 0); m_layout->addWidget(button, row, 1); m_widgets.insert(obj, qMakePair(label, button)); } //------------------------------------------------------------------------------ void MessageRecipients::removeRecipient(QASObject* obj) { QPair widgets = m_widgets[obj]; m_layout->removeWidget(widgets.first); m_layout->removeWidget(widgets.second); widgets.first->deleteLater(); widgets.second->deleteLater(); m_list.removeAll(obj); m_widgets.remove(obj); } //------------------------------------------------------------------------------ void MessageRecipients::clear() { QLayoutItem* item; while ((item = m_layout->takeAt(0)) != 0) { if (dynamic_cast(item)) { QWidget* w = item->widget(); delete w; } delete item; } m_list.clear(); m_widgets.clear(); } //------------------------------------------------------------------------------ void MessageRecipients::onRemoveClicked(QObject* qObj) { QASObject* obj = qobject_cast(qObj); if (!obj) return; removeRecipient(obj); } pumpa-0.9.2/src/messagewindow.cpp0000644000175000017500000004125712653206625015525 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "messagewindow.h" #include "pumpa_defines.h" #include "pumpasettings.h" #include "util.h" #include #include #include //------------------------------------------------------------------------------ const int max_picture_size = 160; //------------------------------------------------------------------------------ PictureLabel::PictureLabel(QWidget* parent) : QLabel(parent) {} //------------------------------------------------------------------------------ void PictureLabel::setOriginalPixmap(QPixmap p) { m_originalPixmap = p; setPixmap(p); } //------------------------------------------------------------------------------ void PictureLabel::resizeEvent(QResizeEvent* event) { if (m_originalPixmap.isNull()) return; QSize size = event->size(); setPixmap(m_originalPixmap.scaled(size, Qt::KeepAspectRatio)); } //------------------------------------------------------------------------------ MessageWindow::MessageWindow(PumpaSettings* s, const RecipientList* rl, QWidget* parent) : QDialog(parent), m_addressLayout(NULL), m_editing(false), m_isReply(false), m_obj(NULL), m_s(s), m_rl(rl) { setMinimumSize(QSize(400,400)); setWindowTitle(CLIENT_FANCY_NAME); m_infoLabel = new QLabel(this); m_markdownCheckBox = new QCheckBox(tr("Use Markdown"), this); connect(m_markdownCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onMarkdownChecked(int))); m_markupLabel = new QLabel(this); m_markupLabel->setText(QString("" + tr("[help]") + ""). arg(MARKUP_DOC_URL)); m_markupLabel->setOpenExternalLinks(true); m_markupLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); m_infoLayout = new QHBoxLayout; m_infoLayout->addWidget(m_infoLabel); m_infoLayout->addStretch(); m_infoLayout->addWidget(m_markdownCheckBox); m_infoLayout->addWidget(m_markupLabel); m_toRecipients = new MessageRecipients(this); m_ccRecipients = new MessageRecipients(this); m_toLabel = new QLabel(tr("To:"), this); m_ccLabel = new QLabel(tr("Cc:"), this); m_addressLayout = new QFormLayout; m_addressLayout->addRow(m_toLabel, m_toRecipients); m_addressLayout->addRow(m_ccLabel, m_ccRecipients); m_addressLayout->setContentsMargins(0, 0, 0, 0); m_addPictureButton = new TextToolButton(this); connect(m_addPictureButton, SIGNAL(clicked()), this, SLOT(onAddPicture())); m_removePictureButton = new TextToolButton(tr("&Remove picture"), this); connect(m_removePictureButton, SIGNAL(clicked()), this, SLOT(onRemovePicture())); m_addToButton = new TextToolButton(tr("+ &To"), this); connect(m_addToButton, SIGNAL(clicked()), this, SLOT(onAddTo())); m_addCcButton = new TextToolButton(tr("+ &Cc"), this); connect(m_addCcButton, SIGNAL(clicked()), this, SLOT(onAddCc())); m_pictureButtonLayout = new QHBoxLayout; m_pictureButtonLayout->addWidget(m_addPictureButton, 0, Qt::AlignTop); m_pictureButtonLayout->addWidget(m_removePictureButton, 0, Qt::AlignTop); m_pictureButtonLayout->addStretch(); m_pictureButtonLayout->addWidget(m_addToButton, 0, Qt::AlignTop); m_pictureButtonLayout->addWidget(m_addCcButton, 0, Qt::AlignTop); m_pictureLabel = new PictureLabel(this); // m_pictureLabel->setScaledContents(true); m_pictureLabel->setMaximumSize(max_picture_size, max_picture_size); m_pictureLabel->setFocusPolicy(Qt::NoFocus); m_pictureLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken); m_title = new QLineEdit(this); #if QT_VERSION >= 0x040700 m_title->setPlaceholderText(tr("Title (optional)")); #endif connect(m_title, SIGNAL(textChanged(const QString&)), this, SLOT(updatePreview())); m_textEdit = new MessageEdit(m_s, this); connect(m_textEdit, SIGNAL(ready()), this, SLOT(accept())); connect(m_textEdit, SIGNAL(textChanged()), this, SLOT(updatePreview())); connect(m_textEdit, SIGNAL(addRecipient(QASActor*)), this, SLOT(onAddRecipient(QASActor*))); m_previewLabel = new RichTextLabel; m_previewLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum); m_previewArea = new QScrollArea(this); m_previewArea->setWidget(m_previewLabel); m_previewArea->setWidgetResizable(true); m_charCountLabel = new QLabel(this); QWidget* qw = new QWidget(this); QVBoxLayout* qwLayout = new QVBoxLayout; qwLayout->addWidget(m_textEdit); qwLayout->addWidget(m_charCountLabel); qwLayout->setMargin(0); qw->setLayout(qwLayout); m_splitter = new QSplitter(Qt::Vertical, this); m_splitter->addWidget(qw); m_splitter->addWidget(m_previewArea); m_splitter->setChildrenCollapsible(false); m_layout = new QVBoxLayout; m_layout->addLayout(m_infoLayout); m_layout->addLayout(m_addressLayout); m_layout->addLayout(m_pictureButtonLayout); m_layout->addWidget(m_pictureLabel, 0, Qt::AlignHCenter); m_layout->addWidget(m_title); m_layout->addWidget(m_splitter); m_cancelButton = new QPushButton(tr("Ca&ncel")); connect(m_cancelButton, SIGNAL(clicked()), this, SLOT(reject())); m_previewButton = new QPushButton(tr("&Preview")); connect(m_previewButton, SIGNAL(clicked()), this, SLOT(togglePreview())); m_sendButton = new QPushButton(tr("&Send message")); connect(m_sendButton, SIGNAL(clicked()), this, SLOT(accept())); m_sendButton->setDefault(true); m_buttonLayout = new QHBoxLayout; m_buttonLayout->addWidget(m_cancelButton); m_buttonLayout->addWidget(m_previewButton); m_buttonLayout->addWidget(m_sendButton); m_layout->addLayout(m_buttonLayout); setLayout(m_layout); m_textEdit->setFocus(Qt::OtherFocusReason); QTextCursor cursor = m_textEdit->textCursor(); cursor.movePosition(QTextCursor::End); m_textEdit->setTextCursor(cursor); } //------------------------------------------------------------------------------ void MessageWindow::onMarkdownChecked(int state) { if (!m_editing) { if (state == Qt::Unchecked) { m_s->useMarkdown(false); } else { m_s->useMarkdown(true); } } updatePreview(); } //------------------------------------------------------------------------------ void MessageWindow::setCompletions(const MessageEdit::completion_t* completions) { if (m_textEdit) m_textEdit->setCompletions(completions); } //------------------------------------------------------------------------------ void MessageWindow::addToRecipientList(QString name, QASObject* obj) { m_recipientList.append(name); if (!m_recipientSelection.contains(name)) m_recipientSelection.insert(name, obj); } //------------------------------------------------------------------------------ void MessageWindow::addRecipientWindow(MessageRecipients* mr, QString title) { QInputDialog* dialog = new QInputDialog(this); // dialog->setOption(QInputDialog::UseListViewForComboBoxItems); dialog->setComboBoxItems(m_recipientList); dialog->setComboBoxEditable(true); dialog->setLabelText(title); dialog->setInputMode(QInputDialog::TextInput); dialog->setWindowTitle(CLIENT_FANCY_NAME); dialog->exec(); if (dialog->result() == QDialog::Accepted) { QString text = dialog->textValue(); if (m_recipientSelection.contains(text)) mr->addRecipient(m_recipientSelection[text]); } } //------------------------------------------------------------------------------ void MessageWindow::onAddTo() { addRecipientWindow(m_toRecipients, tr("Select recipient (To)")); } //------------------------------------------------------------------------------ void MessageWindow::onAddCc() { addRecipientWindow(m_ccRecipients, tr("Select recipient (Cc)")); } //------------------------------------------------------------------------------ void MessageWindow::onAddRecipient(QASActor* actor) { m_toRecipients->addRecipient(actor); } //------------------------------------------------------------------------------ void MessageWindow::initWindow(QString title, QString buttonText, bool showRecipients) { setWindowTitle(QString(CLIENT_FANCY_NAME) + " - " + title); m_infoLabel->setText(title); m_sendButton->setText(buttonText); m_markdownCheckBox->setChecked(m_s->useMarkdown() || m_editing); m_previewArea->setVisible(m_s->showPreview()); m_toRecipients->setVisible(showRecipients); m_ccRecipients->setVisible(showRecipients); m_toLabel->setVisible(showRecipients); m_ccLabel->setVisible(showRecipients); m_addToButton->setVisible(showRecipients); m_addCcButton->setVisible(showRecipients); m_charCountLabel->setVisible(m_s->showCharCount()); updatePreview(true); updateAddPicture(); } //------------------------------------------------------------------------------ void MessageWindow::newMessage(QASObject* obj, QASObjectList* to, QASObjectList* cc) { if (m_editing) clear(); m_editing = false; QASObject* origObj = obj; if (obj && !m_s->commentOnComments() && obj->inReplyTo()) obj = obj->inReplyTo(); m_isReply = (obj != NULL); m_obj = obj; if (m_isReply) m_title->clear(); QString title = m_isReply ? tr("Post a reply") : tr("Post a note"); QString buttonText = m_isReply ? tr("&Send comment") : tr("&Send post"); initWindow(title, buttonText, true); m_recipientList.clear(); for (int i=0; isize(); ++i) { QASObject* obj = m_rl->at(i); addToRecipientList(obj->displayName() + " (list)", obj); } m_markdownCheckBox->setEnabled(true); const MessageEdit::completion_t* completions = m_textEdit->getCompletions(); MessageEdit::completion_t::const_iterator it = completions->constBegin(); for (; it != completions->constEnd(); ++it) addToRecipientList(it.key(), it.value()); if (!m_isReply) { // A new post, use default recipients setDefaultRecipients(m_toRecipients, m_s->defaultToAddress()); setDefaultRecipients(m_ccRecipients, m_s->defaultCcAddress()); } else { // For a reply, copy recipients from parent copyRecipients(m_toRecipients, to); copyRecipients(m_ccRecipients, cc); // add original post author to recipients if (obj->author() && !obj->author()->isYou()) m_toRecipients->addRecipient(obj->author()); // if this is a reply to a comment add comment author to // recipients as well if (origObj != obj && origObj->author() && !origObj->author()->isYou()) m_toRecipients->addRecipient(origObj->author()); } } //------------------------------------------------------------------------------ void MessageWindow::editMessage(QASObject* obj) { m_editing = true; m_obj = obj; QString type = obj->type(); m_isReply = type == "comment"; QString title = tr("Edit object"); // we do this for the sake of easier translation if (type == "note") title = tr("Edit post"); else if (type == "comment") title = tr("Edit comment"); else if (type == "image") title = tr("Edit image post"); QString buttonText = tr("&Update object"); // we do this for the sake of easier translation if (type == "note") buttonText = tr("&Update post"); else if (type == "comment") buttonText = tr("&Update comment"); else if (type == "image") buttonText = tr("&Update image post"); m_markdownCheckBox->setEnabled(false); m_textEdit->setPlainText(obj->content()); m_title->setText(obj->displayName()); initWindow(title, buttonText, false); } //------------------------------------------------------------------------------ void MessageWindow::copyRecipients(MessageRecipients* mr, QASObjectList* ol) { mr->clear(); if (ol) { RecipientList rl = ol->toRecipientList(); for (int i=0; iasActor(); bool isYou = actor && actor->isYou(); if (!isYou && !rl[i]->type().isEmpty()) mr->addRecipient(rl[i]); } } } //------------------------------------------------------------------------------ void MessageWindow::setDefaultRecipients(MessageRecipients* mr, int defAddress) { mr->clear(); if (defAddress == RECIPIENT_PUBLIC) mr->addRecipient(m_rl->at(0)); if (defAddress == RECIPIENT_FOLLOWERS) mr->addRecipient(m_rl->at(1)); } //------------------------------------------------------------------------------ void MessageWindow::clear() { m_imageFileName = ""; m_textEdit->clear(); m_title->clear(); m_previewLabel->setText(""); } //------------------------------------------------------------------------------ void MessageWindow::showEvent(QShowEvent*) { m_textEdit->setFocus(Qt::OtherFocusReason); m_textEdit->selectAll(); activateWindow(); } //------------------------------------------------------------------------------ void MessageWindow::accept() { m_textEdit->hideCompletion(); QString msg = m_textEdit->toPlainText(); RecipientList to = m_toRecipients->recipients(); RecipientList cc = m_ccRecipients->recipients(); QString title = m_title->text(); if (m_editing) { emit sendEdit(m_obj, msg, title); } else if (m_isReply) { emit sendReply(m_obj, msg, to, cc); } else { if (m_imageFileName.isEmpty()) { emit sendMessage(msg, title, to, cc); } else { emit sendImage(msg, title, m_imageFileName, to, cc); } } QDialog::accept(); } //------------------------------------------------------------------------------ void MessageWindow::onAddPicture() { QString fileName = QFileDialog::getOpenFileName(this, tr("Select Image"), "", tr("Image files (*.png *.jpg *.jpeg *.gif)" ";;All files (*.*)")); if (!fileName.isEmpty()) { m_imageFileName = fileName; updateAddPicture(); } } //------------------------------------------------------------------------------ void MessageWindow::onRemovePicture() { m_imageFileName = ""; updateAddPicture(); } //------------------------------------------------------------------------------ void MessageWindow::updateAddPicture() { if (m_isReply || m_editing) { m_addPictureButton->setVisible(false); m_removePictureButton->setVisible(false); m_pictureLabel->setVisible(false); m_title->setVisible(!m_isReply); m_removePictureButton->setVisible(false); return; } QPixmap p; if (!m_imageFileName.isEmpty()) { p.load(m_imageFileName); if (p.isNull()) { QMessageBox::critical(this, tr("Sorry!"), tr("That file didn't appear to be an image.")); m_imageFileName = ""; } } m_addPictureButton->setVisible(true); m_title->setVisible(true); if (m_imageFileName.isEmpty()) { m_addPictureButton->setText(tr("&Add picture")); m_removePictureButton->setVisible(false); m_pictureLabel->setVisible(false); } else { QPixmap scaledPixmap = p.scaled(max_picture_size, max_picture_size, Qt::KeepAspectRatio); m_pictureLabel->setOriginalPixmap(scaledPixmap); m_addPictureButton->setText(tr("&Change picture")); m_removePictureButton->setVisible(true); m_pictureLabel->setVisible(true); } } //------------------------------------------------------------------------------ void MessageWindow::updatePreview(bool force) { QString previewText = addTextMarkup(m_textEdit->toPlainText(), m_s->useMarkdown() || m_editing); if (m_charCountLabel->isVisible() || force) { QString strippedText = previewText; strippedText.remove(QRegExp(HTML_TAG_REGEX)); #ifdef DEBUG_MARKUP qDebug() << "STRIPPEDTEXT" << strippedText; #endif QString ccText = QString(tr("Characters: %1")).arg(strippedText.count()); m_charCountLabel->setText(ccText); } if (m_previewArea->isVisible() || force) { QString titleText = processTitle(m_title->text(), true); if (!titleText.isEmpty()) previewText = "

    " + titleText + "

    " + previewText; m_previewLabel->setText(previewText); } } //------------------------------------------------------------------------------ void MessageWindow::togglePreview() { bool visible = !m_previewArea->isVisible(); m_previewArea->setVisible(visible); m_s->showPreview(visible); updatePreview(); m_textEdit->setFocus(Qt::OtherFocusReason); } pumpa-0.9.2/src/qasobjectlist.cpp0000644000175000017500000000712312653206625015512 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "qasobjectlist.h" #include "qasactor.h" #include "util.h" #include //------------------------------------------------------------------------------ QMap QASObjectList::s_objectLists; void QASObjectList::clearCache() { deleteMap(s_objectLists); } //------------------------------------------------------------------------------ QASObjectList::QASObjectList(QString url, QObject* parent) : QASAbstractObjectList(QAS_OBJECTLIST, url, parent), m_isReplies(false) { #ifdef DEBUG_QAS qDebug() << "new ObjectList" << m_url; #endif } //------------------------------------------------------------------------------ QASAbstractObject* QASObjectList::getAbstractObject(QVariantMap json, QObject* parent) { if (json["objectType"].toString() == "person") return QASActor::getActor(json, parent); return QASObject::getObject(json, parent); } //------------------------------------------------------------------------------ QASObjectList* QASObjectList::initObjectList(QString url, QObject* parent) { if (s_objectLists.contains(url)) return s_objectLists[url]; QASObjectList* ol = new QASObjectList(url, parent); s_objectLists.insert(url, ol); return ol; } //------------------------------------------------------------------------------ QASObjectList* QASObjectList::getObjectList(QVariantMap json, QObject* parent, int id) { QString url = json["url"].toString(); // if (url.isEmpty()) // return NULL; QASObjectList* ol = s_objectLists.contains(url) ? s_objectLists[url] : new QASObjectList(url, parent); if (!url.isEmpty()) s_objectLists.insert(url, ol); ol->update(json, id & QAS_OLDER); return ol; } //------------------------------------------------------------------------------ QASObjectList* QASObjectList::getObjectList(QVariantList json, QObject* parent, int id) { QVariantMap jmap; jmap["totalItems"] = json.size(); jmap["items"] = json; return getObjectList(jmap, parent, id); } //------------------------------------------------------------------------------ void QASObjectList::update(QVariantMap json, bool older, bool) { // if (m_isReplies && json.contains("items")) { // m_item_set.clear(); // m_items.clear(); // } QASAbstractObjectList::update(json, older); } //------------------------------------------------------------------------------ RecipientList QASObjectList::toRecipientList() const { RecipientList rl; for (size_t i=0; iasActor(); if (actor && actor->isYou()) return true; } return false; } pumpa-0.9.2/src/pumpasettings.cpp0000644000175000017500000000637012653206625015551 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include #include "pumpasettings.h" #include "pumpa_defines.h" #include "util.h" PumpaSettings* PumpaSettings::s_settings = NULL; //------------------------------------------------------------------------------ PumpaSettings::PumpaSettings(QString filename, QObject* parent) : QObject(parent) { if (filename.isEmpty()) m_s = new QSettings(CLIENT_NAME, CLIENT_NAME, this); else m_s = new QSettings(filename, QSettings::IniFormat, this); m_firstStart = !QFile(m_s->fileName()).exists(); QFile::setPermissions(m_s->fileName(), QFile::ReadOwner | QFile::WriteOwner); } //------------------------------------------------------------------------------ PumpaSettings* PumpaSettings::getSettings(bool create, QString filename, QObject* parent) { if (s_settings == NULL && create) s_settings = new PumpaSettings(filename, parent); return s_settings; } //------------------------------------------------------------------------------ QVariant PumpaSettings::getValue(QString name, QVariant defaultValue, QString group) const { m_s->beginGroup(group); QVariant v = m_s->value(name, defaultValue); m_s->endGroup(); return v; } //------------------------------------------------------------------------------ QString PumpaSettings::siteUrl() const { return siteUrlFixer(getValue("site_url", "", "Account").toString()); } //------------------------------------------------------------------------------ int PumpaSettings::reloadTime() const { int reloadTime = getValue("reload_time", 1, "General").toInt(); if (reloadTime < 1) reloadTime = 1; return reloadTime; } //------------------------------------------------------------------------------ int PumpaSettings::highlightFeeds() const { return getValue("highlight_feeds", 0, "General").toInt(); } //------------------------------------------------------------------------------ int PumpaSettings::popupFeeds() const { return getValue("popup_feeds", 0, "General").toInt(); } //------------------------------------------------------------------------------ // Setters //------------------------------------------------------------------------------ void PumpaSettings::setValue(QString name, QVariant value, QString group) { m_s->beginGroup(group); m_s->setValue(name, value); m_s->endGroup(); } //------------------------------------------------------------------------------ void PumpaSettings::useTrayIcon(bool b) { bool old = useTrayIcon(); setValue("use_tray_icon", b); if (old != b) emit trayIconChanged(); } pumpa-0.9.2/src/qaspell.h0000644000175000017500000000265012653206625013751 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ /** QASpell Tries to be a wrapper around libaspell, following this: http://aspell.net/man-html/Through-the-C-API.html#Through-the-C-API **/ #ifndef _QASPELL_H_ #define _QASPELL_H_ #include #ifdef USE_ASPELL #include class QASpell : public QObject { public: QASpell(QObject* parent=0); ~QASpell(); bool checkWord(const QString& word) const; QStringList suggestions(const QString& word) const; static void setLocale(QString locale); protected: AspellConfig* spell_config; AspellSpeller* spell_checker; bool checksOK() const; bool ok; static QString s_locale; }; #else // dummy class class QASpell : public QObject { public: QASpell() {} ~QASpell() {} }; #endif #endif /* _QASPELL_H_ */ pumpa-0.9.2/src/fancyhighlighter.h0000644000175000017500000000203312653206625015622 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef FANCY_HIGHLIGHTER_H #define FANCY_HIGHLIGHTER_H #include #include "qaspell.h" #include "util.h" class FancyHighlighter : public QSyntaxHighlighter { public: FancyHighlighter(QTextDocument* doc, QASpell* checker); protected: void highlightBlock(const QString& text); QASpell* m_checker; }; #endif /* FANCY_HIGHLIGHTER_H */ pumpa-0.9.2/src/activitywidget.cpp0000644000175000017500000001065712653206625015711 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "activitywidget.h" #include "texttoolbutton.h" #include "pumpa_defines.h" #include //------------------------------------------------------------------------------ ActivityWidget::ActivityWidget(QASActivity* a, bool fullObject, QWidget* parent) : ObjectWidgetWithSignals(parent), m_objectWidget(NULL), m_activity(NULL) { #ifdef DEBUG_WIDGETS qDebug() << "Creating ActivityWidget"; #endif m_textLabel = new RichTextLabel(this); connect(m_textLabel, SIGNAL(linkHovered(const QString&)), this, SIGNAL(linkHovered(const QString&))); m_objectWidget = new ObjectWidget(NULL, this); ObjectWidgetWithSignals::connectSignals(m_objectWidget, this); connect(m_objectWidget, SIGNAL(showContext(QASObject*)), this, SIGNAL(showContext(QASObject*))); QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_textLabel, 0, Qt::AlignTop); layout->addWidget(m_objectWidget, 0, Qt::AlignTop); layout->addWidget(new QLabel("
    ")); changeObject(a, fullObject); setLayout(layout); } //------------------------------------------------------------------------------ ActivityWidget::~ActivityWidget() { #ifdef DEBUG_WIDGETS qDebug() << "Deleting ActivityWidget" << m_activity->id(); #endif } //------------------------------------------------------------------------------ void ActivityWidget::changeObject(QASAbstractObject* aObj, bool fullObject) { m_activity = qobject_cast(aObj); if (!m_activity) return; const QString verb = m_activity->verb(); QASObject* obj = m_activity->object(); QString objType = obj->type(); m_objectWidget->changeObject(obj, fullObject); m_objectWidget->setActivity(m_activity); bool objectVisible = !obj->content().isEmpty() || !obj->displayName().isEmpty() || (objType == "image" && !obj->imageUrl().isEmpty()) || obj->isDeleted(); m_objectWidget->setVisible(objectVisible); updateText(); } //------------------------------------------------------------------------------ void ActivityWidget::onObjectChanged() { updateText(); } //------------------------------------------------------------------------------ void ActivityWidget::refreshTimeLabels() { if (m_objectWidget) m_objectWidget->refreshTimeLabels(); } //------------------------------------------------------------------------------ void ActivityWidget::updateText() { QString verb = m_activity->verb(); QString text = m_activity->content(); QString objType = m_activity->object()->type(); QString generatorName = m_activity->generatorName(); if (!generatorName.isEmpty() && (verb != "share")) text += QString(tr(" via %1")).arg(generatorName); if (verb == "share") text = QString::fromUtf8("\342\231\273") + " " + text; if (verb == "post") { QString toStr = recipientsToString(m_activity->to()); QString ccStr = recipientsToString(m_activity->cc()); if (!toStr.isEmpty()) text += " " + tr("To:") +" " + toStr; if (!ccStr.isEmpty()) text += " " + tr("CC:") + " " + ccStr; } m_textLabel->setText(text); } //------------------------------------------------------------------------------ QString ActivityWidget::recipientsToString(QASObjectList* rec) { if (!rec) return ""; QStringList ret; for (size_t i=0; isize(); ++i) { QASObject* r = rec->at(i); if (r->type() == "collection" && r->id() == PUBLIC_RECIPIENT_ID) { ret << tr("Public"); } else { QString name = r->displayName(); QString url = r->url(); if (name.isEmpty()) continue; if (url.isEmpty()) ret << name; else ret << QString("%2").arg(url).arg(name); } } return ret.join(", "); } pumpa-0.9.2/src/texttoolbutton.cpp0000644000175000017500000000227412653206625015763 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "texttoolbutton.h" TextToolButton::TextToolButton(QWidget* parent) : QToolButton(parent) { setup(); } //------------------------------------------------------------------------------ TextToolButton::TextToolButton(QString text, QWidget* parent) : QToolButton(parent) { setup(); setText(text); } //------------------------------------------------------------------------------ void TextToolButton::setup() { setToolButtonStyle(Qt::ToolButtonTextOnly); setFocusPolicy(Qt::NoFocus); } pumpa-0.9.2/src/messagerecipients.h0000644000175000017500000000264312653206625016024 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef MESSAGE_RECIPIENTS_H #define MESSAGE_RECIPIENTS_H #include #include #include #include #include #include "qasactor.h" //------------------------------------------------------------------------------ class MessageRecipients : public QWidget { Q_OBJECT public: MessageRecipients(QWidget* parent=0); void clear(); void addRecipient(QASObject* obj); void removeRecipient(QASObject* obj); RecipientList recipients() const { return m_list; } private slots: void onRemoveClicked(QObject*); private: RecipientList m_list; QGridLayout* m_layout; QSignalMapper* m_buttonMapper; QMap > m_widgets; }; #endif pumpa-0.9.2/src/qasactor.h0000644000175000017500000000454212653206625014127 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _QASACTOR_H_ #define _QASACTOR_H_ #include "qasobject.h" #include #include //------------------------------------------------------------------------------ class QASActor : public QASObject { Q_OBJECT private: QASActor(QString id, QObject* parent); public: static void clearCache(); static QASActor* getActor(QVariantMap json, QObject* parent); virtual void update(QVariantMap json); QString webFinger() const { return m_webFinger; } QString displayNameOrWebFinger() const; QString preferredUsername() const { return m_preferredUsername; } QString displayNameOrYou() const { return isYou() ? "You" : displayName(); } void setDisplayName(QString s) { m_displayName = s; } bool isYou() const { return m_isYou; } void setYou(); bool followed() const; void setFollowed(bool b); bool followedKnown() const { return s_followed_known || m_followed_set; } static void setFollowedKnown(); void followedIsKnown(); QString summary() const { return m_summary; } void setSummary(QString s) { m_summary = s; } QString location() const { return m_location; } void setLocation(QString l) { m_location = l; } bool isHidden() const; void setHidden(bool b); static void setHiddenAuthors(QStringList sl); static QStringList getHiddenAuthors() { return s_hiddenAuthors.toList(); } private: static bool s_followed_known; bool m_followed; bool m_followed_json; bool m_followed_set; bool m_isYou; QString m_summary; QString m_location; QString m_webFinger; QString m_preferredUsername; static QMap s_actors; static QSet s_hiddenAuthors; }; #endif /* _QASACTOR_H_ */ pumpa-0.9.2/src/qasactivity.cpp0000644000175000017500000000710412653206625015203 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "qasactivity.h" #include "util.h" #include //------------------------------------------------------------------------------ QMap QASActivity::s_activities; void QASActivity::clearCache() { deleteMap(s_activities); } //------------------------------------------------------------------------------ QASActivity::QASActivity(QString id, QObject* parent) : QASAbstractObject(QAS_ACTIVITY, parent), m_id(id), m_object(NULL), m_actor(NULL), m_to(NULL), m_cc(NULL) { #ifdef DEBUG_QAS qDebug() << "new Activity" << m_id; #endif } //------------------------------------------------------------------------------ void QASActivity::update(QVariantMap json) { #ifdef DEBUG_QAS qDebug() << "updating Activity" << m_id; #endif bool ch = false; updateVar(json, m_verb, "verb", ch); updateVar(json, m_url, "url", ch); updateVar(json, m_content, "content", ch); if (json.contains("actor")) { m_actor = QASActor::getActor(json["actor"].toMap(), parent()); //connectSignals(m_actor); } if (json.contains("object")) { m_object = QASObject::getObject(json["object"].toMap(), parent(), isLikeVerb(m_verb) || isShareVerb(m_verb)); //connectSignals(m_object); if (!m_object->author()) m_object->setAuthor(m_actor); m_object->setPostingActivity(this); } updateVar(json, m_published, "published", ch); updateVar(json, m_updated, "updated", ch); updateVar(json, m_generatorName, "generator", "displayName", ch); if (m_object && m_object->inReplyTo()) m_object->inReplyTo()->addReply(m_object); if (isLikeVerb(m_verb) && m_object && m_actor) m_object->addLike(m_actor, !m_verb.startsWith("un")); if (m_verb == "share" && m_object && m_actor) m_object->addShare(m_actor); if (json.contains("to")) m_to = QASObjectList::getObjectList(json["to"].toList(), parent()); if (json.contains("cc")) m_cc = QASObjectList::getObjectList(json["cc"].toList(), parent()); if (ch) emit changed(); } //------------------------------------------------------------------------------ QASActivity* QASActivity::getActivity(QVariantMap json, QObject* parent) { QString id = json["id"].toString(); if (id.isEmpty()) return NULL; QASActivity* act = s_activities.contains(id) ? s_activities[id] : new QASActivity(id, parent); s_activities.insert(id, act); act->update(json); return act; } //------------------------------------------------------------------------------ bool QASActivity::hasTo() const { return m_to && m_to->size(); } //------------------------------------------------------------------------------ bool QASActivity::hasCc() const { return m_cc && m_cc->size(); } //------------------------------------------------------------------------------ bool QASActivity::skipNotify() const { return generatorName() == "pumpbridge"; } pumpa-0.9.2/src/qasobject.h0000644000175000017500000001174012653206625014263 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _QASOBJECT_H_ #define _QASOBJECT_H_ #include "qasabstractobject.h" class QASActor; class QASActorList; class QASObjectList; class QASActivity; //------------------------------------------------------------------------------ class QASLocation : public QObject { Q_OBJECT public: QASLocation(QObject* parent); bool isEmpty() const { return m_displayName.isEmpty(); } void update(QVariantMap json); QString osmURL(int level) const { return QString("http://www.openstreetmap.org/#map=%1/%2/%3"). arg(level).arg(m_latitude).arg(m_longitude); } bool hasPosition() const { return m_hasPosition; } double latitude() const { return m_latitude; } double longitude() const { return m_longitude; } QString displayName() const { return m_displayName; } private: void updatePosition(QVariantMap json); double m_latitude; double m_longitude; QString m_displayName; bool m_hasPosition; }; //------------------------------------------------------------------------------ class QASObject : public QASAbstractObject { Q_OBJECT protected: QASObject(QString id, QObject* parent); public: static void clearCache(); static int cacheItems() { return s_objects.count(); } static int objectsUnconnected(); int connections() const; static QASObject* getObject(QVariantMap json, QObject* parent, bool ignoreLike=false); static QASObject* getObject(QString id) { return s_objects.contains(id) ? s_objects[id] : NULL; } virtual void update(QVariantMap json, bool ignoreLike); virtual void update(QVariantMap json) { update(json, false); } QASActor* asActor(); qint64 sortInt() const { return sortIntByDateTime(m_updated); } QDateTime updatedDate() const { return m_updated; } QString id() const { return m_id; } QString content() const; QString type() const { return m_objectType; } QString url() const { return m_url; } QString imageUrl() const { return m_imageUrl; } QString fullImageUrl() const { return m_fullImageUrl; } QString displayName() const { return m_displayName; } virtual QString apiLink() const; QString proxyUrl() const { return m_proxyUrl; } QString urlOrProxy() const { return m_proxyUrl.isEmpty() ? m_url : m_proxyUrl; } QString fileUrl() const { return m_fileUrl; } QString mimeType() const { return m_mimeType; } QASLocation* location() const { return m_location; } QString streamUrl(bool orig=false) const { return m_streamUrlProxy.isEmpty() || orig ? m_streamUrl : m_streamUrlProxy; } QDateTime published() const { return m_published; } void toggleLiked(); bool liked() const { return m_liked; } size_t numLikes() const; void addLike(QASActor* actor, bool like); QASActorList* likes() const { return m_likes; } bool shared() const { return m_shared; } size_t numShares() const; void addShare(QASActor* actor); QASActorList* shares() const { return m_shares; } size_t numReplies() const; QASObjectList* replies() const { return m_replies; } void addReply(QASObject* obj); QASActor* author() const { return m_author; } void setAuthor(QASActor* a) { m_author = a; } QASObject* inReplyTo() const { return m_inReplyTo; } // currently just a minimal variant needed for the API e.g. when // favouriting the object QVariantMap toJson() const; virtual bool isDeleted() const { return !m_deleted.isNull(); } QDateTime deletedDate() const { return m_deleted; } QString excerpt() const; QASActivity* postingActivity() const { return m_postingActivity; } void setPostingActivity(QASActivity* a) { m_postingActivity = a; } protected: QString deletedText() const; QString m_id; QString m_content; bool m_liked; bool m_shared; QString m_objectType; QString m_url; QString m_imageUrl; QString m_fullImageUrl; QString m_displayName; QString m_apiLink; QString m_proxyUrl; QString m_streamUrl; QString m_streamUrlProxy; QString m_fileUrl; QString m_mimeType; QDateTime m_published; QDateTime m_updated; QDateTime m_deleted; QASLocation* m_location; QASObject* m_inReplyTo; QASActor* m_author; QASObjectList* m_replies; QASActorList* m_likes; QASActorList* m_shares; QASActivity* m_postingActivity; static QMap s_objects; }; typedef QList RecipientList; #endif /* _QASOBJECT_H_ */ pumpa-0.9.2/src/oauthwizard.h0000644000175000017500000000516412653206625014654 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _OAUTHWIZARD_H_ #define _OAUTHWIZARD_H_ #include #include #include #include #include #include "QtKOAuth" //------------------------------------------------------------------------------ class OAuthFirstPage : public QWizardPage { Q_OBJECT public: OAuthFirstPage(QWidget* parent=0); void setMessage(QString msg); signals: void committed(QString, QString); protected: virtual bool validatePage(); virtual bool isComplete() const; private: bool splitAccountId(QString& username, QString& server) const; QLabel* m_messageLabel; }; //------------------------------------------------------------------------------ class OAuthSecondPage : public QWizardPage { Q_OBJECT public: OAuthSecondPage(QWidget* parent=0); protected: virtual bool validatePage(); signals: void committed(QString, QString); }; //------------------------------------------------------------------------------ class OAuthWizard : public QWizard { Q_OBJECT public: OAuthWizard(QNetworkAccessManager* nam, KQOAuthManager* oam, QWidget* parent=0); signals: void clientRegistered(QString, QString, QString, QString); void accessTokenReceived(QString, QString); private slots: void onFirstPageCommitted(QString, QString); void onSecondPageCommitted(QString, QString); void onOAuthClientRegDone(); void onTemporaryTokenReceived(QString temporaryToken, QString temporaryTokenSecret); void onAccessTokenReceived(QString token, QString tokenSecret); private: void notifyMessage(QString); void errorMessage(QString); void registerOAuthClient(); void getOAuthAccess(); OAuthFirstPage* p1; OAuthSecondPage* p2; KQOAuthManager *m_oam; KQOAuthRequest *m_oar; QNetworkAccessManager *m_nam; QString m_server, m_username; QString m_clientId, m_clientSecret; int m_clientRegTryCount; }; #endif /* _OAUTHWIZARD_H_ */ pumpa-0.9.2/src/pumpasettingsdialog.h0000644000175000017500000000401412653206625016367 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _PUMPASETTINGSDIALOG_H_ #define _PUMPASETTINGSDIALOG_H_ #include #include #include #include #include #include #include #include #include #include #include "pumpasettings.h" class PumpaSettingsDialog : public QDialog { Q_OBJECT public: PumpaSettingsDialog(PumpaSettings* settings, QWidget* parent=0); static int comboIndexToFeedInt(int i) { return comboIndexConverter(i, false); } static int feedIntToComboIndex(int i) { return comboIndexConverter(i, true); } signals: void newAccount(); private slots: void onOKClicked(); // void on_buttonBox_accepted(); protected: void setVisible(bool visible); private slots: void onAuthButtonClicked(); private: static int comboIndexConverter(int i, bool backwards=false); void updateUI(); PumpaSettings* s; QLabel* m_currentAccountLabel; QPushButton* m_authButton; QSpinBox* m_updateTimeSpinBox; QCheckBox* m_useIconCheckBox; QCheckBox* m_showCharCountCheckBox; QDialogButtonBox* m_buttonBox; QComboBox* m_highlightComboBox; QComboBox* m_popupComboBox; QComboBox* m_defaultToComboBox; QComboBox* m_defaultCcComboBox; // QFormLayout* m_formLayout; QVBoxLayout* m_layout; }; #endif /* _PUMPASETTINGSDIALOG_H_ */ pumpa-0.9.2/src/kQOAuth/0000755000175000017500000000000012653206625013450 5ustar matsmatspumpa-0.9.2/src/kQOAuth/kqoauthauthreplyserver_p.h0000644000175000017500000000302312653206625020777 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ // Note this class shouldn't be copied or used and the implementation might change later. #ifndef KQOAUTHAUTHREPLYSERVER_P_H #define KQOAUTHAUTHREPLYSERVER_P_H #include "kqoauthauthreplyserver.h" #include #include class KQOAUTH_EXPORT KQOAuthAuthReplyServerPrivate: public QObject { Q_OBJECT public: KQOAuthAuthReplyServerPrivate( KQOAuthAuthReplyServer * parent ); ~KQOAuthAuthReplyServerPrivate(); QMultiMap parseQueryParams(QByteArray *sdata); public Q_SLOTS: void onIncomingConnection(); void onBytesReady(); public: KQOAuthAuthReplyServer * q_ptr; Q_DECLARE_PUBLIC(KQOAuthAuthReplyServer); QTcpSocket *socket; }; #endif // KQOAUTHAUTHREPLYSERVER_P_H pumpa-0.9.2/src/kQOAuth/kqoauthrequest_xauth_p.h0000644000175000017500000000212012653206625020431 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #ifndef KQOAUTHREQUEST_XAUTH_P_H #define KQOAUTHREQUEST_XAUTH_P_H #include "kqoauthglobals.h" class KQOAuthRequest; class KQOAUTH_EXPORT KQOAuthRequest_XAuthPrivate { public: KQOAuthRequest_XAuthPrivate(); ~KQOAuthRequest_XAuthPrivate(); }; #endif // KQOAUTHREQUEST_XAUTH_P_H pumpa-0.9.2/src/kQOAuth/kqoauthutils.h0000644000175000017500000000204012653206625016352 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #ifndef KQOAUTHUTILS_H #define KQOAUTHUTILS_H #include "kqoauthglobals.h" class QString; class KQOAUTH_EXPORT KQOAuthUtils { public: static QString hmac_sha1(const QString &message, const QString &key); }; #endif // KQOAUTHUTILS_H pumpa-0.9.2/src/kQOAuth/kqoauthrequest.cpp0000644000175000017500000004340112653206625017243 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #include #include #include #include #include #include #include #include "kqoauthrequest.h" #include "kqoauthrequest_p.h" #include "kqoauthutils.h" #include "kqoauthglobals.h" //////////// Private d_ptr implementation ///////// KQOAuthRequestPrivate::KQOAuthRequestPrivate() : timeout(0) { } KQOAuthRequestPrivate::~KQOAuthRequestPrivate() { } // This method will not include the "oauthSignature" paramater, since it is calculated from these parameters. void KQOAuthRequestPrivate::prepareRequest() { // If parameter list is not empty, we don't want to insert these values by // accident a second time. So giving up. if( !requestParameters.isEmpty() ) { return; } switch ( requestType ) { case KQOAuthRequest::TemporaryCredentials: requestParameters.append( qMakePair( OAUTH_KEY_CALLBACK, oauthCallbackUrl.isEmpty() ? "oob" : oauthCallbackUrl.toString() )); requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE_METHOD, oauthSignatureMethod) ); requestParameters.append( qMakePair( OAUTH_KEY_CONSUMER_KEY, oauthConsumerKey )); requestParameters.append( qMakePair( OAUTH_KEY_VERSION, oauthVersion )); requestParameters.append( qMakePair( OAUTH_KEY_TIMESTAMP, this->oauthTimestamp() )); requestParameters.append( qMakePair( OAUTH_KEY_NONCE, this->oauthNonce() )); break; case KQOAuthRequest::AccessToken: requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE_METHOD, oauthSignatureMethod )); requestParameters.append( qMakePair( OAUTH_KEY_CONSUMER_KEY, oauthConsumerKey )); requestParameters.append( qMakePair( OAUTH_KEY_VERSION, oauthVersion )); requestParameters.append( qMakePair( OAUTH_KEY_TIMESTAMP, this->oauthTimestamp() )); requestParameters.append( qMakePair( OAUTH_KEY_NONCE, this->oauthNonce() )); requestParameters.append( qMakePair( OAUTH_KEY_VERIFIER, oauthVerifier )); requestParameters.append( qMakePair( OAUTH_KEY_TOKEN, oauthToken )); break; case KQOAuthRequest::AuthorizedRequest: requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE_METHOD, oauthSignatureMethod )); requestParameters.append( qMakePair( OAUTH_KEY_CONSUMER_KEY, oauthConsumerKey )); requestParameters.append( qMakePair( OAUTH_KEY_VERSION, oauthVersion )); requestParameters.append( qMakePair( OAUTH_KEY_TIMESTAMP, this->oauthTimestamp() )); requestParameters.append( qMakePair( OAUTH_KEY_NONCE, this->oauthNonce() )); requestParameters.append( qMakePair( OAUTH_KEY_TOKEN, oauthToken )); break; default: break; } } void KQOAuthRequestPrivate::signRequest() { QString signature = this->oauthSignature(); requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE, signature) ); } QString KQOAuthRequestPrivate::oauthSignature() { /** * http://oauth.net/core/1.0/#anchor16 * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] where the * Signature Base String is the text and the key is the concatenated values (each first encoded per Parameter * Encoding) of the Consumer Secret and Token Secret, separated by an ‘&’ character (ASCII code 38) even if empty. **/ QByteArray baseString = this->requestBaseString(); QString secret = QString(QUrl::toPercentEncoding(oauthConsumerSecretKey)) + "&" + QString(QUrl::toPercentEncoding(oauthTokenSecret)); QString signature = KQOAuthUtils::hmac_sha1(baseString, secret); if (debugOutput) { qDebug() << "========== KQOAuthRequest has the following signature:"; qDebug() << " * Signature : " << QUrl::toPercentEncoding(signature) << "\n"; } return QString( QUrl::toPercentEncoding(signature) ); } bool normalizedParameterSort(const QPair &left, const QPair &right) { QString keyLeft = left.first; QString valueLeft = left.second; QString keyRight = right.first; QString valueRight = right.second; if(keyLeft == keyRight) { return (valueLeft < valueRight); } else { return (keyLeft < keyRight); } } QByteArray KQOAuthRequestPrivate::requestBaseString() { QByteArray baseString; // Every request has these as the commont parameters. baseString.append( oauthHttpMethodString.toUtf8() + "&"); // HTTP method baseString.append( QUrl::toPercentEncoding( oauthRequestEndpoint.toString(QUrl::RemoveQuery) ) + "&" ); // The path and query components QList< QPair > baseStringParameters; baseStringParameters.append(requestParameters); baseStringParameters.append(additionalParameters); // Sort the request parameters. These parameters have been // initialized earlier. qSort(baseStringParameters.begin(), baseStringParameters.end(), normalizedParameterSort ); // Last append the request parameters correctly encoded. baseString.append( encodedParamaterList(baseStringParameters) ); if (debugOutput) { qDebug() << "========== KQOAuthRequest has the following base string:"; qDebug() << baseString << "\n"; } return baseString; } QByteArray KQOAuthRequestPrivate::encodedParamaterList(const QList< QPair > ¶meters) { QByteArray resultList; bool first = true; QPair parameter; // Do the debug output. if (debugOutput) { qDebug() << "========== KQOAuthRequest has the following parameters:"; } foreach (parameter, parameters) { if(!first) { resultList.append( "&" ); } else { first = false; } // Here we don't need to explicitely encode the strings to UTF-8 since // QUrl::toPercentEncoding() takes care of that for us. resultList.append( QUrl::toPercentEncoding(parameter.first) // Parameter key + "=" + QUrl::toPercentEncoding(parameter.second) // Parameter value ); if (debugOutput) { qDebug() << " * " << parameter.first << " : " << parameter.second; } } if (debugOutput) { qDebug() << "\n"; } return QUrl::toPercentEncoding(resultList); } QString KQOAuthRequestPrivate::oauthTimestamp() const { // This is basically for unit tests only. In most cases we don't set the nonce beforehand. if (!oauthTimestamp_.isEmpty()) { return oauthTimestamp_; } #if QT_VERSION >= 0x040700 return QString::number(QDateTime::currentDateTimeUtc().toTime_t()); #else return QString::number(QDateTime::currentDateTime().toUTC().toTime_t()); #endif } QString KQOAuthRequestPrivate::oauthNonce() const { // This is basically for unit tests only. In most cases we don't set the nonce beforehand. if (!oauthNonce_.isEmpty()) { return oauthNonce_; } return QString::number(qrand()); } bool KQOAuthRequestPrivate::validateRequest() const { switch ( requestType ) { case KQOAuthRequest::TemporaryCredentials: if (oauthRequestEndpoint.isEmpty() || oauthConsumerKey.isEmpty() || oauthNonce_.isEmpty() || oauthSignatureMethod.isEmpty() || oauthTimestamp_.isEmpty() || oauthVersion.isEmpty()) { return false; } return true; case KQOAuthRequest::AccessToken: if (oauthRequestEndpoint.isEmpty() || oauthVerifier.isEmpty() || oauthConsumerKey.isEmpty() || oauthNonce_.isEmpty() || oauthSignatureMethod.isEmpty() || oauthTimestamp_.isEmpty() || oauthToken.isEmpty() || oauthTokenSecret.isEmpty() || oauthVersion.isEmpty()) { return false; } return true; case KQOAuthRequest::AuthorizedRequest: if (oauthRequestEndpoint.isEmpty() || oauthConsumerKey.isEmpty() || oauthNonce_.isEmpty() || oauthSignatureMethod.isEmpty() || oauthTimestamp_.isEmpty() || oauthToken.isEmpty() || oauthTokenSecret.isEmpty() || oauthVersion.isEmpty()) { return false; } return true; default: return false; } // We should not come here. return false; } //////////// Public implementation //////////////// KQOAuthRequest::KQOAuthRequest(QObject *parent) : QObject(parent), d_ptr(new KQOAuthRequestPrivate) { Q_D(KQOAuthRequest); d_ptr->debugOutput = false; // No debug output by default. connect(&(d->timer), SIGNAL(timeout()), this, SIGNAL(requestTimedout())); } KQOAuthRequest::~KQOAuthRequest() { delete d_ptr; } void KQOAuthRequest::initRequest(KQOAuthRequest::RequestType type, const QUrl &requestEndpoint) { Q_D(KQOAuthRequest); if (!requestEndpoint.isValid()) { qWarning() << "Endpoint URL is not valid. Ignoring. This request might not work."; return; } if (type < 0 || type > KQOAuthRequest::AuthorizedRequest) { qWarning() << "Invalid request type. Ignoring. This request might not work."; return; } // Clear the request clearRequest(); // Set smart defaults. d->requestType = type; d->oauthRequestEndpoint = requestEndpoint; d->oauthTimestamp_ = d->oauthTimestamp(); d->oauthNonce_ = d->oauthNonce(); this->setSignatureMethod(KQOAuthRequest::HMAC_SHA1); this->setHttpMethod(KQOAuthRequest::POST); d->oauthVersion = "1.0"; // Currently supports only version 1.0 d->contentType = "application/x-www-form-urlencoded"; } void KQOAuthRequest::setConsumerKey(const QString &consumerKey) { Q_D(KQOAuthRequest); d->oauthConsumerKey = consumerKey; } void KQOAuthRequest::setConsumerSecretKey(const QString &consumerSecretKey) { Q_D(KQOAuthRequest); d->oauthConsumerSecretKey = consumerSecretKey; } void KQOAuthRequest::setCallbackUrl(const QUrl &callbackUrl) { Q_D(KQOAuthRequest); d->oauthCallbackUrl = callbackUrl; } void KQOAuthRequest::setSignatureMethod(KQOAuthRequest::RequestSignatureMethod requestMethod) { Q_D(KQOAuthRequest); QString requestMethodString; switch (requestMethod) { case KQOAuthRequest::PLAINTEXT: requestMethodString = "PLAINTEXT"; break; case KQOAuthRequest::HMAC_SHA1: requestMethodString = "HMAC-SHA1"; break; case KQOAuthRequest::RSA_SHA1: requestMethodString = "RSA-SHA1"; break; default: // We should not come here qWarning() << "Invalid signature method set."; break; } d->oauthSignatureMethod = requestMethodString; } void KQOAuthRequest::setTokenSecret(const QString &tokenSecret) { Q_D(KQOAuthRequest); d->oauthTokenSecret = tokenSecret; } void KQOAuthRequest::setToken(const QString &token) { Q_D(KQOAuthRequest); d->oauthToken = token; } void KQOAuthRequest::setVerifier(const QString &verifier) { Q_D(KQOAuthRequest); d->oauthVerifier = verifier; } void KQOAuthRequest::setHttpMethod(KQOAuthRequest::RequestHttpMethod httpMethod) { Q_D(KQOAuthRequest); QString requestHttpMethodString; switch (httpMethod) { case KQOAuthRequest::GET: requestHttpMethodString = "GET"; break; case KQOAuthRequest::POST: requestHttpMethodString = "POST"; break; case KQOAuthRequest::PUT: requestHttpMethodString = "PUT"; break; default: qWarning() << "Invalid HTTP method set."; break; } d->oauthHttpMethod = httpMethod; d->oauthHttpMethodString = requestHttpMethodString; } KQOAuthRequest::RequestHttpMethod KQOAuthRequest::httpMethod() const { Q_D(const KQOAuthRequest); return d->oauthHttpMethod; } void KQOAuthRequest::setAdditionalParameters(const KQOAuthParameters &additionalParams) { Q_D(KQOAuthRequest); QList additionalKeys = additionalParams.keys(); QList additionalValues = additionalParams.values(); int i=0; foreach(QString key, additionalKeys) { QString value = additionalValues.at(i); d->additionalParameters.append( qMakePair(key, value) ); i++; } } KQOAuthParameters KQOAuthRequest::additionalParameters() const { Q_D(const KQOAuthRequest); QMultiMap additionalParams; for(int i=0; iadditionalParameters.size(); i++) { additionalParams.insert(d->additionalParameters.at(i).first, d->additionalParameters.at(i).second); } return additionalParams; } KQOAuthRequest::RequestType KQOAuthRequest::requestType() const { Q_D(const KQOAuthRequest); return d->requestType; } QUrl KQOAuthRequest::requestEndpoint() const { Q_D(const KQOAuthRequest); return d->oauthRequestEndpoint; } QList KQOAuthRequest::requestParameters() { Q_D(KQOAuthRequest); QList requestParamList; d->prepareRequest(); if (!isValid() ) { qWarning() << "Request is not valid! I will still sign it, but it will probably not work."; } d->signRequest(); QPair requestParam; QString param; QString value; foreach (requestParam, d->requestParameters) { param = requestParam.first; value = requestParam.second; if (param != OAUTH_KEY_SIGNATURE) { value = QUrl::toPercentEncoding(value); } requestParamList.append(QString(param + "=\"" + value +"\"").toUtf8()); } return requestParamList; } QString KQOAuthRequest::contentType() { Q_D(const KQOAuthRequest); return d->contentType; } void KQOAuthRequest::setContentType(const QString &contentType) { Q_D(KQOAuthRequest); d->contentType = contentType; } int KQOAuthRequest::contentLength() { Q_D(const KQOAuthRequest); return d->contentLength; } void KQOAuthRequest::setContentLength(int contentLength) { Q_D(KQOAuthRequest); d->contentLength = contentLength; } QByteArray KQOAuthRequest::rawData() { Q_D(const KQOAuthRequest); return d->postRawData; } void KQOAuthRequest::setRawData(const QByteArray &rawData) { Q_D(KQOAuthRequest); d->postRawData = rawData; } QByteArray KQOAuthRequest::requestBody() const { Q_D(const KQOAuthRequest); QByteArray postBodyContent; bool first = true; for(int i=0; i < d->additionalParameters.size(); i++) { if(!first) { postBodyContent.append("&"); } else { first = false; } QString key = d->additionalParameters.at(i).first; QString value = d->additionalParameters.at(i).second; postBodyContent.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value)); } return postBodyContent; } bool KQOAuthRequest::isValid() const { Q_D(const KQOAuthRequest); return d->validateRequest(); } void KQOAuthRequest::setTimeout(int timeoutMilliseconds) { Q_D(KQOAuthRequest); d->timeout = timeoutMilliseconds; } void KQOAuthRequest::clearRequest() { Q_D(KQOAuthRequest); d->oauthRequestEndpoint = ""; d->oauthHttpMethodString = ""; d->oauthConsumerKey = ""; d->oauthConsumerSecretKey = ""; d->oauthToken = ""; d->oauthTokenSecret = ""; d->oauthSignatureMethod = ""; d->oauthCallbackUrl = ""; d->oauthVerifier = ""; d->oauthTimestamp_ = ""; d->oauthNonce_ = ""; d->requestParameters.clear(); d->additionalParameters.clear(); d->timeout = 0; } void KQOAuthRequest::setEnableDebugOutput(bool enabled) { Q_D(KQOAuthRequest); d->debugOutput = enabled; } /** * Protected implementations for inherited classes */ bool KQOAuthRequest::validateXAuthRequest() const { Q_D(const KQOAuthRequest); if (d->oauthRequestEndpoint.isEmpty() || d->oauthConsumerKey.isEmpty() || d->oauthNonce_.isEmpty() || d->oauthSignatureMethod.isEmpty() || d->oauthTimestamp_.isEmpty() || d->oauthVersion.isEmpty()) { return false; } return true; } /** * Private implementations for friend classes */ QString KQOAuthRequest::consumerKeyForManager() const { Q_D(const KQOAuthRequest); return d->oauthConsumerKey; } QString KQOAuthRequest::consumerKeySecretForManager() const { Q_D(const KQOAuthRequest); return d->oauthConsumerSecretKey; } QUrl KQOAuthRequest::callbackUrlForManager() const { Q_D(const KQOAuthRequest); return d->oauthCallbackUrl; } void KQOAuthRequest::requestTimerStart() { Q_D(KQOAuthRequest); if (d->timeout > 0) { d->timer.start(d->timeout); } } void KQOAuthRequest::requestTimerStop() { Q_D(KQOAuthRequest); if( d->timer.isActive() ) d->timer.stop(); } pumpa-0.9.2/src/kQOAuth/kqoauthrequest_xauth.cpp0000644000175000017500000000521612653206625020456 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #include #include "kqoauthrequest_xauth_p.h" #include "kqoauthrequest_xauth.h" /** * Private d_ptr implementations. */ KQOAuthRequest_XAuthPrivate::KQOAuthRequest_XAuthPrivate() { } KQOAuthRequest_XAuthPrivate::~KQOAuthRequest_XAuthPrivate() { } /** * Public implementations. */ KQOAuthRequest_XAuth::KQOAuthRequest_XAuth(QObject *parent) : KQOAuthRequest(parent), d_ptr(new KQOAuthRequest_XAuthPrivate) { } bool KQOAuthRequest_XAuth::isValid() const { // An xAuth can never request temporary credentials. if (requestType() == KQOAuthRequest::TemporaryCredentials) { qWarning() << "XAuth request cannot be of type KQOAuthRequest::TemporaryCredentials. Aborting."; return false; } // Access token must always be retrieved using the POST HTTP method. if (requestType() == KQOAuthRequest::AccessToken && httpMethod() != KQOAuthRequest::POST) { qWarning() << "Access tokens must be fetched using the POST HTTP method. Aborting."; return false; } if (!xauth_parameters_set) { qWarning() << "No XAuth parameters set. Aborting."; return false; } // And then check the validity of the XAuth request. // Provided by the base class as a protected method for us. return validateXAuthRequest(); } void KQOAuthRequest_XAuth::setXAuthLogin(const QString &username, const QString &password) { if (username.isEmpty() || password.isEmpty()) { qWarning() << "Username or password cannot be empty. Aborting."; return; } xauth_parameters_set = true; KQOAuthParameters xauthParams; xauthParams.insert("x_auth_username", username); xauthParams.insert("x_auth_password", password); xauthParams.insert("x_auth_mode", "client_auth"); setAdditionalParameters(xauthParams); } pumpa-0.9.2/src/kQOAuth/kqoauthglobals.h0000644000175000017500000000367612653206625016655 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #ifndef KQOAUTHGLOBALS_H #define KQOAUTHGLOBALS_H #include // The Q_DECL_EXPORT/Q_DECL_IMPORT things are only needed when compiling // a shared library or against one in Windows. We aren't doing either in // Pumpa. // // Credit goes to Tim Schumacher for finding // a solution for this. #define KQOAUTH_EXPORT // #if defined(KQOAUTH) // # define KQOAUTH_EXPORT Q_DECL_EXPORT // #else // # define KQOAUTH_EXPORT Q_DECL_IMPORT // #endif //////////// Static constant definitions /////////// const QString OAUTH_KEY_CONSUMER("oauth_consumer"); const QString OAUTH_KEY_CONSUMER_KEY("oauth_consumer_key"); const QString OAUTH_KEY_TOKEN("oauth_token"); const QString OAUTH_KEY_TOKEN_SECRET("oauth_token_secret"); const QString OAUTH_KEY_SIGNATURE_METHOD("oauth_signature_method"); const QString OAUTH_KEY_TIMESTAMP("oauth_timestamp"); const QString OAUTH_KEY_NONCE("oauth_nonce"); const QString OAUTH_KEY_SIGNATURE("oauth_signature"); const QString OAUTH_KEY_CALLBACK("oauth_callback"); const QString OAUTH_KEY_VERIFIER("oauth_verifier"); const QString OAUTH_KEY_VERSION("oauth_version"); #endif // KQOAUTHGLOBALS_H pumpa-0.9.2/src/kQOAuth/Makefile0000644000175000017500000000003012653206625015101 0ustar matsmatsall: $(MAKE) -C ../../ pumpa-0.9.2/src/kQOAuth/kqoauthrequest.h0000644000175000017500000001135112653206625016707 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #ifndef KQOAUTHREQUEST_H #define KQOAUTHREQUEST_H #include #include #include #include "kqoauthglobals.h" typedef QMultiMap KQOAuthParameters; class KQOAuthRequestPrivate; class KQOAUTH_EXPORT KQOAuthRequest : public QObject { Q_OBJECT public: explicit KQOAuthRequest(QObject *parent = 0); ~KQOAuthRequest(); enum RequestType { TemporaryCredentials = 0, AccessToken, AuthorizedRequest }; enum RequestSignatureMethod { PLAINTEXT = 0, HMAC_SHA1, RSA_SHA1 }; enum RequestHttpMethod { GET = 0, POST, PUT }; /** * These methods can be overridden in child classes which are different types of * OAuth requests. */ // Validate the request of this type. virtual bool isValid() const; /** * These methods are OAuth request type specific and not overridden in child * classes. * NOTE: Refactorting still a TODO */ // Initialize the request of this type. void initRequest(KQOAuthRequest::RequestType type, const QUrl &requestEndpoint); void setConsumerKey(const QString &consumerKey); void setConsumerSecretKey(const QString &consumerSecretKey); // Mandatory methods for acquiring a request token void setCallbackUrl(const QUrl &callbackUrl); // Mandator methods for acquiring a access token void setTokenSecret(const QString &tokenSecret); void setToken(const QString &token); void setVerifier(const QString &verifier); // Request signature method to use - HMAC_SHA1 currently only supported void setSignatureMethod(KQOAuthRequest::RequestSignatureMethod = KQOAuthRequest::HMAC_SHA1); // Request's HTTP method. void setHttpMethod(KQOAuthRequest::RequestHttpMethod = KQOAuthRequest::POST); KQOAuthRequest::RequestHttpMethod httpMethod() const; // Sets the timeout for this request. If the timeout expires, the signal "requestTimedout" will be // emitted. The KQOAuthManager will then call the abort() function from QNetworkReply associated with this request // 0 = If set to zero, timeout is disabled. // TODO: Do we need some request ID now? void setTimeout(int timeoutMilliseconds); // Additional optional parameters to the request. void setAdditionalParameters(const KQOAuthParameters &additionalParams); KQOAuthParameters additionalParameters() const; QList requestParameters(); // This will return all request's parameters in the raw format given // to the QNetworkRequest. QByteArray requestBody() const; // This will return the POST body as given to the QNetworkRequest. KQOAuthRequest::RequestType requestType() const; QUrl requestEndpoint() const; void setContentType(const QString &contentType); QString contentType(); void setContentLength(int contentLength); int contentLength(); void setRawData(const QByteArray &rawData); QByteArray rawData(); void clearRequest(); // Enable verbose debug output for request content. void setEnableDebugOutput(bool enabled); Q_SIGNALS: // This signal is emited if the request is not completed before the request's timeout // value has expired. void requestTimedout(); protected: bool validateXAuthRequest() const; private: KQOAuthRequestPrivate * const d_ptr; Q_DECLARE_PRIVATE(KQOAuthRequest); Q_DISABLE_COPY(KQOAuthRequest); // These classes are only for the internal use of KQOAuthManager so it can // work with the opaque request. QString consumerKeyForManager() const; QString consumerKeySecretForManager() const; QUrl callbackUrlForManager() const; // This method is for timeout handling by the KQOAuthManager. void requestTimerStart(); void requestTimerStop(); friend class KQOAuthManager; #ifdef UNIT_TEST friend class Ut_KQOAuth; #endif }; #endif // KQOAUTHREQUEST_H pumpa-0.9.2/src/kQOAuth/kqoauthutils.cpp0000644000175000017500000000477712653206625016730 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #include #include #include #include #include "kqoauthutils.h" QString KQOAuthUtils::hmac_sha1(const QString &message, const QString &key) { QByteArray keyBytes = key.toLatin1(); int keyLength; // Lenght of key word const int blockSize = 64; // Both MD5 and SHA-1 have a block size of 64. keyLength = keyBytes.size(); // If key is longer than block size, we need to hash the key if (keyLength > blockSize) { QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(keyBytes); keyBytes = hash.result(); } /* http://tools.ietf.org/html/rfc2104 - (1) */ // Create the opad and ipad for the hash function. QByteArray ipad; QByteArray opad; ipad.fill( 0, blockSize); opad.fill( 0, blockSize); ipad.replace(0, keyBytes.length(), keyBytes); opad.replace(0, keyBytes.length(), keyBytes); /* http://tools.ietf.org/html/rfc2104 - (2) & (5) */ for (int i=0; i<64; i++) { ipad[i] = ipad[i] ^ 0x36; opad[i] = opad[i] ^ 0x5c; } QByteArray workArray; workArray.clear(); workArray.append(ipad, 64); /* http://tools.ietf.org/html/rfc2104 - (3) */ workArray.append(message.toLatin1()); /* http://tools.ietf.org/html/rfc2104 - (4) */ QByteArray sha1 = QCryptographicHash::hash(workArray, QCryptographicHash::Sha1); /* http://tools.ietf.org/html/rfc2104 - (6) */ workArray.clear(); workArray.append(opad, 64); workArray.append(sha1); sha1.clear(); /* http://tools.ietf.org/html/rfc2104 - (7) */ sha1 = QCryptographicHash::hash(workArray, QCryptographicHash::Sha1); return QString(sha1.toBase64()); } pumpa-0.9.2/src/kQOAuth/kqoauthmanager.h0000644000175000017500000002156512653206625016641 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #ifndef KQOAUTHMANAGER_H #define KQOAUTHMANAGER_H #include #include #include #include #include "kqoauthrequest.h" class KQOAuthRequest; class KQOAuthManagerThread; class KQOAuthManagerPrivate; class QNetworkAccessManager; class QUrl; class QByteArray; class KQOAUTH_EXPORT KQOAuthManager : public QObject { Q_OBJECT public: enum KQOAuthError { NoError, // No error NetworkError, // Network error: timeout, cannot connect. RequestEndpointError, // Request endpoint is not valid. RequestValidationError, // Request is not valid: some parameter missing? RequestUnauthorized, // Authorization error: trying to access a resource without tokens. RequestError, // The given request to KQOAuthManager is invalid: NULL?, ManagerError // Manager error, cannot use for sending requests. }; explicit KQOAuthManager(QObject *parent = 0); ~KQOAuthManager(); KQOAuthError lastError(); QNetworkReply* getReply(KQOAuthRequest* request); /** * The manager executes the given request. It takes the HTTP parameters from the * request and uses QNetworkAccessManager to submit the HTTP request to the net. * When the request is done it will emit signal requestReady(QByteArray networkReply). * NOTE: At the moment there is no timeout for the request. */ void executeRequest(KQOAuthRequest *request); void executeAuthorizedRequest(KQOAuthRequest *request, int id); /** * Indicates to the user that KQOAuthManager should handle user authorization by * opening the user's default browser and parsing the reply from the service. * By setting the parameter to true, KQOAuthManager will store intermediate results * of the OAuth 1.0 process in its own opaque request. This information is used in * the user authorization process and also when calling sendAuthorizedRequest(). * NOTE: You need to set this to true if you want to use getUserAccessTokens() or * sendAuthorizedRequest(). */ void setHandleUserAuthorization(bool set); /** * Returns true if the KQOAuthManager has retrieved the oauth_token value. Otherwise * return false. */ bool hasTemporaryToken(); /** * Returns true if the user has authorized us to use the protected resources. Otherwise * returns false. * NOTE: In order for KQOAuthManager to know if the user has authorized us to use the * protected resources, KQOAuthManager must be in control of the user authorization * process. Hence, this returns true if setHandleUserAuthorization() is set to true * and the user is authorized with getUserAuthorization(). */ bool isVerified(); /** * Returns true if KQOAuthManager has the access token and hence can access the protected * resources. Otherwise returns false. * NOTE: In order for KQOAuthManager to know if we have access to protected resource * KQOAuthManager must be in control of the user authorization process and requesting * the acess token. Hence, this returns true if setHandleUserAuthorization() is set to true * and the user is authorized with getUserAuthorization() and the access token must be retrieved * with getUserAccessTokens. */ bool isAuthorized(); /** * This is a convenience API for authorizing the user. * The call will open the user's default browser, setup a local HTTP server and parse the reply from the * service after the user has authorized us to access protected resources. If the user authorizes * us to access protected resources, the verifier token is stored in KQOAuthManager for further use. * In order to use this method, you must set setHandleUserAuthorization() to true. */ void getUserAuthorization(QUrl authorizationEndpoint); /** * This is a convenience API for retrieving the access token in exchange for the temporary token and the * verifier. * This call will create a KQOAuthRequest and use the previously stored temporary token and verifier to * exchange for the access token, which will be used to access the protected resources. * Note that in order to use this method, KQOAuthManager must be in control of the user authorization process. * Set setHandleUserAuthorization() to true and retrieve user authorization with void getUserAuthorization. */ void getUserAccessTokens(QUrl accessTokenEndpoint); /** * This is a conveience API for setting the token verifier. * If setHandleUserAuthorization() is set to false you need to call this function before calling * getUserAccessTokens() */ void verifyToken(const QString &token, const QString &verifier); /** * Sends a request to the protected resources. Parameters for the request are service specific and * are given to the 'requestParameters' as parameters. * Note that in order to use this method, KQOAuthManager must be in control of the user authorization process. * Set setHandleUserAuthorization() to true and retrieve user authorization with void getUserAuthorization. */ void sendAuthorizedRequest(QUrl requestEndpoint, const KQOAuthParameters &requestParameters); /** * Sets a custom QNetworkAccessManager to handle network requests. This method can be useful if the * application is using some proxy settings for example. * The application is responsible for deleting this manager. KQOAuthManager will not delete any * previously given manager. * If the manager is NULL, the manager will not be set and the KQOAuthManager::Error. * If no manager is given, KQOAuthManager will use the default one it will create by itself. */ void setNetworkManager(QNetworkAccessManager *manager); /** * Returns the given QNetworkAccessManager. Returns NULL if none is given. */ QNetworkAccessManager* networkManager() const; Q_SIGNALS: // This signal will be emitted after each request has got a reply. // Parameter is the raw response from the service. void requestReady(QByteArray networkReply); void authorizedRequestReady(QByteArray networkReply, int id); // This signal will be emited when we have an request tokens available // (either temporary resource tokens, or authorization tokens). void receivedToken(QString oauth_token, QString oauth_token_secret); // oauth_token, oauth_token_secret // This signal is emited when temporary tokens are returned from the service. // Note that this signal is also emited in case temporary tokens are not available. void temporaryTokenReceived(QString oauth_token, QString oauth_token_secret); // oauth_token, oauth_token_secret // This signal is emited when the user has authenticated the application to // communicate with the protected resources. Next we need to exchange the // temporary tokens for access tokens. // Note that this signal is also emited if user denies access. void authorizationReceived(QString oauth_token, QString oauth_verifier); // oauth_token, oauth_verifier // This signal is emited when access tokens are received from the service. We are // ready to start communicating with the protected resources. void accessTokenReceived(QString oauth_token, QString oauth_token_secret); // oauth_token, oauth_token_secret // This signal is emited when the authorized request is done. // This ends the kQOAuth interactions. void authorizedRequestDone(); void errorMessage(QString); void sslErrors(QNetworkReply*, QList); private Q_SLOTS: void onRequestReplyReceived( QNetworkReply *reply ); void onAuthorizedRequestReplyReceived( QNetworkReply *reply ); void onVerificationReceived(QMultiMap response); void slotError(QNetworkReply::NetworkError error); void requestTimeout(); private: KQOAuthManagerPrivate *d_ptr; Q_DECLARE_PRIVATE(KQOAuthManager); Q_DISABLE_COPY(KQOAuthManager); }; #endif // KQOAUTHMANAGER_H pumpa-0.9.2/src/kQOAuth/kqoauthauthreplyserver.h0000644000175000017500000000260112653206625020461 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #ifndef KQOAUTHAUTHREPLYSERVER_H #define KQOAUTHAUTHREPLYSERVER_H #include #include "kqoauthglobals.h" class KQOAuthAuthReplyServerPrivate; class KQOAUTH_EXPORT KQOAuthAuthReplyServer : public QTcpServer { Q_OBJECT public: explicit KQOAuthAuthReplyServer(QObject *parent); ~KQOAuthAuthReplyServer(); Q_SIGNALS: void verificationReceived(QMultiMap); private: KQOAuthAuthReplyServerPrivate * const d_ptr; Q_DECLARE_PRIVATE(KQOAuthAuthReplyServer); Q_DISABLE_COPY(KQOAuthAuthReplyServer); }; #endif // KQOAUTHAUTHREPLYSERVER_H pumpa-0.9.2/src/kQOAuth/kqoauthrequest_1.cpp0000644000175000017500000000157212653206625017466 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #include "kqoauthrequest_1.h" KQOAuthRequest_1::KQOAuthRequest_1() { } pumpa-0.9.2/src/kQOAuth/kqoauthrequest_xauth.h0000644000175000017500000000305312653206625020120 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #ifndef KQOAUTHREQUEST_XAUTH_H #define KQOAUTHREQUEST_XAUTH_H #include "kqoauthrequest.h" #include "kqoauthrequest_1.h" class KQOAuthRequest_XAuthPrivate; class KQOAUTH_EXPORT KQOAuthRequest_XAuth : public KQOAuthRequest { Q_OBJECT public: KQOAuthRequest_XAuth(QObject *parent = 0); /** * These methods can be overridden in child classes which are different types of * OAuth requests. */ // Validate the request of this type. bool isValid() const; // Give the xAuth specific parameters. void setXAuthLogin(const QString &username = "", const QString &password = ""); private: KQOAuthRequest_XAuthPrivate * const d_ptr; bool xauth_parameters_set; }; #endif // KQOAUTHREQUEST_XAUTH_H pumpa-0.9.2/src/kQOAuth/kqoauthrequest_p.h0000644000175000017500000000552512653206625017234 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #ifndef KQOAUTHREQUEST_P_H #define KQOAUTHREQUEST_P_H #include "kqoauthglobals.h" #include "kqoauthrequest.h" #include #include #include #include #include #include class KQOAUTH_EXPORT KQOAuthRequestPrivate { public: KQOAuthRequestPrivate(); ~KQOAuthRequestPrivate(); // Helper methods to get the values for the OAuth request parameters. QString oauthTimestamp() const; QString oauthNonce() const; QString oauthSignature(); // Utility methods for making the request happen. void prepareRequest(); void signRequest(); bool validateRequest() const; QByteArray requestBaseString(); QByteArray encodedParamaterList(const QList< QPair > &requestParameters); void insertAdditionalParams(); void insertPostBody(); QUrl oauthRequestEndpoint; KQOAuthRequest::RequestHttpMethod oauthHttpMethod; QString oauthHttpMethodString; QString oauthConsumerKey; QString oauthConsumerSecretKey; QString oauthToken; QString oauthTokenSecret; QString oauthSignatureMethod; QUrl oauthCallbackUrl; QString oauthVersion; QString oauthVerifier; // These will be generated by the helper methods QString oauthTimestamp_; QString oauthNonce_; // User specified additional parameters needed for the request. QList< QPair > additionalParameters; // The raw POST body content as given to the HTTP request. QByteArray postBodyContent; // Protocol parameters. // These parameters are used in the "Authorized" header of the HTTP request. QList< QPair > requestParameters; KQOAuthRequest::RequestType requestType; //The Content-Type HTTP header QString contentType; //The Content-Length HTTP header int contentLength; //Raw data to post if type is not url-encoded QByteArray postRawData; // Timeout for this request in milliseconds. int timeout; QTimer timer; bool debugOutput; }; #endif // KQOAUTHREQUEST_P_H pumpa-0.9.2/src/kQOAuth/kqoauthmanager.cpp0000644000175000017500000006270012653206625017170 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #include #include #if QT_VERSION >= 0x050000 #include #endif #include "kqoauthmanager.h" #include "kqoauthmanager_p.h" ////////////// Private d_ptr implementation //////////////// KQOAuthManagerPrivate::KQOAuthManagerPrivate(KQOAuthManager *parent) : error(KQOAuthManager::NoError) , r(0) , opaqueRequest(new KQOAuthRequest) , q_ptr(parent) , callbackServer(new KQOAuthAuthReplyServer(parent)) , isVerified(false) , isAuthorized(false) , autoAuth(false), networkManager(new QNetworkAccessManager), managerUserSet(false) { } KQOAuthManagerPrivate::~KQOAuthManagerPrivate() { delete opaqueRequest; opaqueRequest = 0; if (!managerUserSet) { delete networkManager; networkManager = 0; } } QList< QPair > KQOAuthManagerPrivate::createQueryParams(const KQOAuthParameters &requestParams) { QList requestKeys = requestParams.keys(); QList requestValues = requestParams.values(); QList< QPair > result; for(int i=0; i KQOAuthManagerPrivate::createTokensFromResponse(QByteArray reply) { QMultiMap result; QString replyString(reply); QStringList parameterPairs = replyString.split('&', QString::SkipEmptyParts); foreach (const QString ¶meterPair, parameterPairs) { QStringList parameter = parameterPair.split('='); result.insert(parameter.value(0), parameter.value(1)); } return result; } bool KQOAuthManagerPrivate::setSuccessfulRequestToken(const QMultiMap &request) { if (currentRequestType == KQOAuthRequest::TemporaryCredentials) { hasTemporaryToken = (!QString(request.value("oauth_token")).isEmpty() && !QString(request.value("oauth_token_secret")).isEmpty()); } else { return false; } if (hasTemporaryToken) { requestToken = QUrl::fromPercentEncoding( QString(request.value("oauth_token")).toLocal8Bit() ); requestTokenSecret = QUrl::fromPercentEncoding( QString(request.value("oauth_token_secret")).toLocal8Bit() ); } return hasTemporaryToken; } bool KQOAuthManagerPrivate::setSuccessfulAuthorized(const QMultiMap &request ) { if (currentRequestType == KQOAuthRequest::AccessToken) { isAuthorized = (!QString(request.value("oauth_token")).isEmpty() && !QString(request.value("oauth_token_secret")).isEmpty()); } else { return false; } if (isAuthorized) { requestToken = QUrl::fromPercentEncoding( QString(request.value("oauth_token")).toLocal8Bit() ); requestTokenSecret = QUrl::fromPercentEncoding( QString(request.value("oauth_token_secret")).toLocal8Bit() ); } return isAuthorized; } void KQOAuthManagerPrivate::emitTokens() { Q_Q(KQOAuthManager); if (this->requestToken.isEmpty() || this->requestTokenSecret.isEmpty()) { error = KQOAuthManager::RequestUnauthorized; } if (currentRequestType == KQOAuthRequest::TemporaryCredentials) { // Signal that we are ready to use the protected resources. emit q->temporaryTokenReceived(this->requestToken, this->requestTokenSecret); } if (currentRequestType == KQOAuthRequest::AccessToken) { // Signal that we are ready to use the protected resources. emit q->accessTokenReceived(this->requestToken, this->requestTokenSecret); } emit q->receivedToken(this->requestToken, this->requestTokenSecret); } bool KQOAuthManagerPrivate::setupCallbackServer() { return callbackServer->listen(); } /////////////// Public implementation //////////////// KQOAuthManager::KQOAuthManager(QObject *parent) : QObject(parent) , d_ptr(new KQOAuthManagerPrivate(this)) { qsrand(QTime::currentTime().msec()); // We need to seed the nonce random number with something. // However, we cannot do this while generating the nonce since // we might get the same seed. So initializing here should be fine. } KQOAuthManager::~KQOAuthManager() { delete d_ptr; } void KQOAuthManager::executeRequest(KQOAuthRequest *request) { Q_D(KQOAuthManager); d->r = request; if (request == 0) { qWarning() << "Request is NULL. Cannot proceed."; d->error = KQOAuthManager::RequestError; return; } if (!request->requestEndpoint().isValid()) { qWarning() << "Request endpoint URL is not valid. Cannot proceed."; d->error = KQOAuthManager::RequestEndpointError; return; } if (!request->isValid()) { qWarning() << "Request is not valid. Cannot proceed."; d->error = KQOAuthManager::RequestValidationError; return; } d->currentRequestType = request->requestType(); QNetworkRequest networkRequest; networkRequest.setUrl( request->requestEndpoint() ); if (d->autoAuth && d->currentRequestType == KQOAuthRequest::TemporaryCredentials) { d->setupCallbackServer(); connect(d->callbackServer, SIGNAL(verificationReceived(QMultiMap)), this, SLOT( onVerificationReceived(QMultiMap))); QString serverString = "http://localhost:"; serverString.append(QString::number(d->callbackServer->serverPort())); request->setCallbackUrl(QUrl(serverString)); } // And now fill the request with "Authorization" header data. QList requestHeaders = request->requestParameters(); QByteArray authHeader; bool first = true; foreach (const QByteArray header, requestHeaders) { if (!first) { authHeader.append(", "); } else { authHeader.append("OAuth "); first = false; } authHeader.append(header); } networkRequest.setRawHeader("Authorization", authHeader); connect(d->networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onRequestReplyReceived(QNetworkReply *)), Qt::UniqueConnection); connect(d->networkManager, SIGNAL(sslErrors(QNetworkReply*, QList)), this, SIGNAL(sslErrors(QNetworkReply*, QList)), Qt::UniqueConnection); disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onAuthorizedRequestReplyReceived(QNetworkReply *))); if (request->httpMethod() == KQOAuthRequest::GET) { // Get the requested additional params as a list of pairs we can give QUrl QList< QPair > urlParams = d->createQueryParams(request->additionalParameters()); // Take the original URL and append the query params to it. QUrl urlWithParams = networkRequest.url(); #if QT_VERSION < 0x050000 urlWithParams.setQueryItems(urlParams); #else QUrlQuery query; query.setQueryItems(urlParams); urlWithParams.setQuery(query); #endif networkRequest.setUrl(urlWithParams); // Submit the request including the params. QNetworkReply *reply = d->networkManager->get(networkRequest); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(slotError(QNetworkReply::NetworkError))); d->requestMap.insert( request, reply ); } else if (request->httpMethod() == KQOAuthRequest::POST || request->httpMethod() == KQOAuthRequest::PUT) { networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, request->contentType()); /* qDebug() << networkRequest.rawHeaderList(); qDebug() << networkRequest.rawHeader("Authorization"); qDebug() << networkRequest.rawHeader("Content-Type"); */ QNetworkReply *reply; if (request->contentType() == "application/x-www-form-urlencoded") { reply = d->networkManager->post(networkRequest, request->requestBody()); } else { reply = d->networkManager->post(networkRequest, request->rawData()); } connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(slotError(QNetworkReply::NetworkError))); d->requestMap.insert( request, reply ); } d->r->requestTimerStart(); } void KQOAuthManager::executeAuthorizedRequest(KQOAuthRequest *request, int id) { Q_D(KQOAuthManager); d->r = request; if (request == 0) { qWarning() << "Request is NULL. Cannot proceed."; d->error = KQOAuthManager::RequestError; return; } if (!request->requestEndpoint().isValid()) { qWarning() << "Request endpoint URL is not valid. Cannot proceed."; d->error = KQOAuthManager::RequestEndpointError; return; } if (!request->isValid()) { qWarning() << "Request is not valid. Cannot proceed."; d->error = KQOAuthManager::RequestValidationError; return; } d->currentRequestType = request->requestType(); QNetworkRequest networkRequest; networkRequest.setUrl( request->requestEndpoint() ); if ( d->currentRequestType != KQOAuthRequest::AuthorizedRequest){ qWarning() << "Not Authorized Request. Cannot proceed"; d->error = KQOAuthManager::RequestError; return; } // And now fill the request with "Authorization" header data. QList requestHeaders = request->requestParameters(); QByteArray authHeader; bool first = true; foreach (const QByteArray header, requestHeaders) { if (!first) { authHeader.append(", "); } else { authHeader.append("OAuth "); first = false; } authHeader.append(header); } networkRequest.setRawHeader("Authorization", authHeader); disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onRequestReplyReceived(QNetworkReply *))); connect(d->networkManager, SIGNAL(sslErrors(QNetworkReply*, QList)), this, SIGNAL(sslErrors(QNetworkReply*, QList)), Qt::UniqueConnection); connect(d->networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onAuthorizedRequestReplyReceived(QNetworkReply*)), Qt::UniqueConnection); QNetworkReply *reply = NULL; if (request->httpMethod() == KQOAuthRequest::GET) { // Get the requested additional params as a list of pairs we can give QUrl QList< QPair > urlParams = d->createQueryParams(request->additionalParameters()); // Take the original URL and append the query params to it. QUrl urlWithParams = networkRequest.url(); #if QT_VERSION < 0x050000 urlWithParams.setQueryItems(urlParams); #else QUrlQuery query; query.setQueryItems(urlParams); urlWithParams.setQuery(query); #endif networkRequest.setUrl(urlWithParams); // Submit the request including the params. reply = d->networkManager->get(networkRequest); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(slotError(QNetworkReply::NetworkError))); connect(request, SIGNAL(requestTimedout()), this, SLOT(requestTimeout())); d->requestMap.insert( request, reply ); } else if (request->httpMethod() == KQOAuthRequest::POST || request->httpMethod() == KQOAuthRequest::PUT) { networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, request->contentType()); int contentLength = request->contentLength(); if (contentLength != -1) networkRequest.setHeader(QNetworkRequest::ContentLengthHeader, contentLength); #ifdef DEBUG_NET_MOAR QList nrhl = networkRequest.rawHeaderList(); for (int i=0; ihttpMethod() == KQOAuthRequest::PUT) { if (request->contentType() == "application/x-www-form-urlencoded") { reply = d->networkManager->put(networkRequest, request->requestBody()); } else { reply = d->networkManager->put(networkRequest, request->rawData()); } } else { if (request->contentType() == "application/x-www-form-urlencoded") { reply = d->networkManager->post(networkRequest, request->requestBody()); } else { reply = d->networkManager->post(networkRequest, request->rawData()); } } connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(slotError(QNetworkReply::NetworkError))); connect(request, SIGNAL(requestTimedout()), this, SLOT(requestTimeout())); d->requestMap.insert( request, reply ); } d->requestIds.insert(reply, id); d->r->requestTimerStart(); } void KQOAuthManager::setHandleUserAuthorization(bool set) { Q_D(KQOAuthManager); d->autoAuth = set; } bool KQOAuthManager::hasTemporaryToken() { Q_D(KQOAuthManager); return d->hasTemporaryToken; } bool KQOAuthManager::isVerified() { Q_D(KQOAuthManager); return d->isVerified; } bool KQOAuthManager::isAuthorized() { Q_D(KQOAuthManager); return d->isAuthorized; } KQOAuthManager::KQOAuthError KQOAuthManager::lastError() { Q_D(KQOAuthManager); return d->error; } void KQOAuthManager::setNetworkManager(QNetworkAccessManager *manager) { Q_D(KQOAuthManager); if (manager == 0) { d->error = KQOAuthManager::ManagerError; return; } if (!d->managerUserSet) { delete d->networkManager; } d->managerUserSet = true; d->networkManager = manager; } QNetworkAccessManager * KQOAuthManager::networkManager() const { Q_D(const KQOAuthManager); if (d->managerUserSet) { return d->networkManager; } else { return NULL; } } //////////// Public convenience API ///////////// void KQOAuthManager::getUserAuthorization(QUrl authorizationEndpoint) { Q_D(KQOAuthManager); if (!d->hasTemporaryToken) { qWarning() << "No temporary tokens retreieved. Cannot get user authorization."; d->error = KQOAuthManager::RequestUnauthorized; return; } if (!authorizationEndpoint.isValid()) { qWarning() << "Authorization endpoint not valid. Cannot proceed."; d->error = KQOAuthManager::RequestEndpointError; return; } d->error = KQOAuthManager::NoError; QPair tokenParam = qMakePair(QString("oauth_token"), QString(d->requestToken)); QUrl openWebPageUrl(authorizationEndpoint.toString(), QUrl::StrictMode); #if QT_VERSION < 0x050000 openWebPageUrl.addQueryItem(tokenParam.first, tokenParam.second); #else QUrlQuery query(openWebPageUrl); query.addQueryItem(tokenParam.first, tokenParam.second); openWebPageUrl.setQuery(query); #endif // Open the user's default browser to the resource authorization page provided // by the service. QDesktopServices::openUrl(openWebPageUrl); qDebug() << "If the web page doesn't open automatically, please go to " "this URL to authorise the client:"; qDebug() << openWebPageUrl.toString(); } void KQOAuthManager::getUserAccessTokens(QUrl accessTokenEndpoint) { Q_D(KQOAuthManager); if (!d->isVerified) { qWarning() << "Not verified. Cannot get access tokens."; d->error = KQOAuthManager::RequestUnauthorized; return; } if (!accessTokenEndpoint.isValid()) { qWarning() << "Endpoint for access token exchange is not valid. Cannot proceed."; d->error = KQOAuthManager::RequestEndpointError; return; } d->error = KQOAuthManager::NoError; d->opaqueRequest->clearRequest(); d->opaqueRequest->initRequest(KQOAuthRequest::AccessToken, accessTokenEndpoint); d->opaqueRequest->setToken(d->requestToken); d->opaqueRequest->setTokenSecret(d->requestTokenSecret); d->opaqueRequest->setVerifier(d->requestVerifier); d->opaqueRequest->setConsumerKey(d->consumerKey); d->opaqueRequest->setConsumerSecretKey(d->consumerKeySecret); executeRequest(d->opaqueRequest); } void KQOAuthManager::verifyToken(const QString &token, const QString &verifier) { QMultiMap params; params.insert("oauth_token", token); params.insert("oauth_verifier", verifier); onVerificationReceived(params); } void KQOAuthManager::sendAuthorizedRequest(QUrl requestEndpoint, const KQOAuthParameters &requestParameters) { Q_D(KQOAuthManager); if (!d->isAuthorized) { qWarning() << "No access tokens retrieved. Cannot send authorized requests."; d->error = KQOAuthManager::RequestUnauthorized; return; } if (!requestEndpoint.isValid()) { qWarning() << "Endpoint for authorized request is not valid. Cannot proceed."; d->error = KQOAuthManager::RequestEndpointError; return; } d->error = KQOAuthManager::NoError; d->opaqueRequest->clearRequest(); d->opaqueRequest->initRequest(KQOAuthRequest::AuthorizedRequest, requestEndpoint); d->opaqueRequest->setAdditionalParameters(requestParameters); d->opaqueRequest->setToken(d->requestToken); d->opaqueRequest->setTokenSecret(d->requestTokenSecret); d->opaqueRequest->setConsumerKey(d->consumerKey); d->opaqueRequest->setConsumerSecretKey(d->consumerKeySecret); executeRequest(d->opaqueRequest); } /////////////// Private slots ////////////////// void KQOAuthManager::onRequestReplyReceived( QNetworkReply *reply ) { Q_D(KQOAuthManager); QNetworkReply::NetworkError networkError = reply->error(); switch (networkError) { case QNetworkReply::NoError: d->error = KQOAuthManager::NoError; break; case QNetworkReply::ContentAccessDenied: case QNetworkReply::AuthenticationRequiredError: d->error = KQOAuthManager::RequestUnauthorized; break; default: d->error = KQOAuthManager::NetworkError; break; } // Let's disconnect this slot first /* disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onRequestReplyReceived(QNetworkReply *))); */ // Read the content of the reply from the network. QByteArray networkReply = reply->readAll(); d->r = d->requestMap.key(reply); if( d->r ) { d->requestMap.remove(d->r); disconnect(d->r, SIGNAL(requestTimedout()), this, SLOT(requestTimeout())); // Stop any timer we have set on the request. d->r->requestTimerStop(); d->currentRequestType = d->r->requestType(); } // Just don't do anything if we didn't get anything useful. if(networkReply.isEmpty()) { reply->deleteLater(); return; } QMultiMap responseTokens; // We need to emit the signal even if we got an error. if (d->error != KQOAuthManager::NoError) { reply->deleteLater(); emit requestReady(networkReply); d->emitTokens(); return; } responseTokens = d->createTokensFromResponse(networkReply); d->opaqueRequest->clearRequest(); d->opaqueRequest->setHttpMethod(KQOAuthRequest::POST); // XXX FIXME: Convenient API does not support GET if (!d->isAuthorized || !d->isVerified) { if (d->setSuccessfulRequestToken(responseTokens)) { qDebug() << "Successfully got request tokens."; d->consumerKey = d->r->consumerKeyForManager(); d->consumerKeySecret = d->r->consumerKeySecretForManager(); d->opaqueRequest->setSignatureMethod(KQOAuthRequest::HMAC_SHA1); d->opaqueRequest->setCallbackUrl(d->r->callbackUrlForManager()); d->emitTokens(); } else if (d->setSuccessfulAuthorized(responseTokens)) { qDebug() << "Successfully got access tokens."; d->opaqueRequest->setSignatureMethod(KQOAuthRequest::HMAC_SHA1); d->emitTokens(); } else if (d->currentRequestType == KQOAuthRequest::AuthorizedRequest) { emit authorizedRequestDone(); } } emit requestReady(networkReply); reply->deleteLater(); // We need to clean this up, after the event processing is done. } void KQOAuthManager::onAuthorizedRequestReplyReceived( QNetworkReply *reply ) { Q_D(KQOAuthManager); QNetworkReply::NetworkError networkError = reply->error(); switch (networkError) { case QNetworkReply::NoError: d->error = KQOAuthManager::NoError; break; case QNetworkReply::ContentAccessDenied: case QNetworkReply::AuthenticationRequiredError: d->error = KQOAuthManager::RequestUnauthorized; break; default: d->error = KQOAuthManager::NetworkError; break; } /* disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onAuthorizedRequestReplyReceived(QNetworkReply *))); */ // Read the content of the reply from the network. QByteArray networkReply = reply->readAll(); int id = d->requestIds.take(reply); d->r = d->requestMap.key(reply); if( d->r ) { d->requestMap.remove(d->r); disconnect(d->r, SIGNAL(requestTimedout()), this, SLOT(requestTimeout())); // Stop any timer we have set on the request. d->r->requestTimerStop(); d->currentRequestType = d->r->requestType(); } // Just don't do anything if we didn't get anything useful. if(networkReply.isEmpty()) { reply->deleteLater(); return; } // We need to emit the signal even if we got an error. if (d->error != KQOAuthManager::NoError) { emit errorMessage("Unable to retrieve "+reply->url().toString()); return; } d->opaqueRequest->clearRequest(); d->opaqueRequest->setHttpMethod(KQOAuthRequest::POST); // XXX FIXME: Convenient API does not support GET if (d->currentRequestType == KQOAuthRequest::AuthorizedRequest) { emit authorizedRequestDone(); } emit authorizedRequestReady(networkReply, id); reply->deleteLater(); } void KQOAuthManager::onVerificationReceived(QMultiMap response) { Q_D(KQOAuthManager); QString token = response.value("oauth_token"); QString verifier = response.value("oauth_verifier"); if (verifier.isEmpty()) { d->error = KQOAuthManager::RequestUnauthorized; } verifier = QUrl::fromPercentEncoding(verifier.toUtf8()); // We get the raw URL response here so we need to convert it back // to plain string so we can percent encode it again later in requests. if (d->error == KQOAuthManager::NoError) { d->requestVerifier = verifier; d->isVerified = true; } emit authorizationReceived(token, verifier); } void KQOAuthManager::slotError(QNetworkReply::NetworkError error) { Q_UNUSED(error) Q_D(KQOAuthManager); d->error = KQOAuthManager::NetworkError; QNetworkReply *reply = qobject_cast(sender()); QByteArray errorResponse = reply->readAll(); d->currentRequestType = KQOAuthRequest::AuthorizedRequest; d->r = d->requestMap.key(reply); if( d->r ) { d->requestMap.remove(d->r); disconnect(d->r, SIGNAL(requestTimedout()), this, SLOT(requestTimeout())); // Stop any timer we have set on the request. d->r->requestTimerStop(); d->currentRequestType = d->r->requestType(); } if( d->requestIds.contains(reply) ) { int id = d->requestIds.value(reply); emit authorizedRequestReady(errorResponse, id); } else if ( d->currentRequestType == KQOAuthRequest::AuthorizedRequest) { // does this signal always have to be emitted if there is an error // or can is it only valid for KQOAuthRequest::AuthorizedRequest? emit authorizedRequestDone(); } else emit requestReady(errorResponse); reply->deleteLater(); } void KQOAuthManager::requestTimeout() { Q_D(KQOAuthManager); KQOAuthRequest *request = qobject_cast(sender()); if( d->requestMap.contains(request)) { qWarning() << "KQOAuthManager::requestTimeout: Calling abort"; d->requestMap.value(request)->abort(); } else qWarning() << "KQOAuthManager::requestTimeout: The KQOAuthRequest was not found"; } QNetworkReply* KQOAuthManager::getReply(KQOAuthRequest* request) { Q_D(KQOAuthManager); KQOAuthRequest* tmp = request; if (!d->requestMap.contains(tmp)) return NULL; return d->requestMap.value(request); } pumpa-0.9.2/src/kQOAuth/kqoauthauthreplyserver.cpp0000644000175000017500000000671412653206625021025 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #include #include #include #if QT_VERSION >= 0x050000 #include #endif #include "kqoauthauthreplyserver.h" #include "kqoauthauthreplyserver_p.h" KQOAuthAuthReplyServerPrivate::KQOAuthAuthReplyServerPrivate(KQOAuthAuthReplyServer *parent): q_ptr(parent) { } KQOAuthAuthReplyServerPrivate::~KQOAuthAuthReplyServerPrivate() { } void KQOAuthAuthReplyServerPrivate::onIncomingConnection() { Q_Q(KQOAuthAuthReplyServer); socket = q->nextPendingConnection(); connect(socket, SIGNAL(readyRead()), this, SLOT(onBytesReady()), Qt::UniqueConnection); } void KQOAuthAuthReplyServerPrivate::onBytesReady() { Q_Q(KQOAuthAuthReplyServer); QByteArray reply; QByteArray content; content.append(""); reply.append("HTTP/1.0 200 OK \r\n"); reply.append("Content-Type: text/html; charset=\"utf-8\"\r\n"); reply.append(QString("Content-Length: %1\r\n").arg(content.size())); reply.append("\r\n"); reply.append(content); socket->write(reply); QByteArray data = socket->readAll(); QMultiMap queryParams = parseQueryParams(&data); socket->disconnectFromHost(); q->close(); emit q->verificationReceived(queryParams); } QMultiMap KQOAuthAuthReplyServerPrivate::parseQueryParams(QByteArray *data) { QString splitGetLine = QString(*data).split("\r\n").first(); // Retrieve the first line with query params. splitGetLine.remove("GET "); // Clean the line from GET splitGetLine.remove("HTTP/1.1"); // From HTTP splitGetLine.remove("\r\n"); // And from rest. splitGetLine.prepend("http://localhost"); // Now, make it a URL QUrl getTokenUrl(splitGetLine); #if QT_VERSION < 0x050000 QList< QPair > tokens = getTokenUrl.queryItems(); // Ask QUrl to do our work. #else QList< QPair > tokens = QUrlQuery(getTokenUrl.query()).queryItems(); // Ask QUrl to do our work. #endif QMultiMap queryParams; QPair tokenPair; foreach (tokenPair, tokens) { queryParams.insert(tokenPair.first.trimmed(), tokenPair.second.trimmed()); } return queryParams; } KQOAuthAuthReplyServer::KQOAuthAuthReplyServer(QObject *parent) : QTcpServer(parent), d_ptr( new KQOAuthAuthReplyServerPrivate(this) ) { Q_D(KQOAuthAuthReplyServer); connect(this, SIGNAL(newConnection()), d, SLOT(onIncomingConnection())); } KQOAuthAuthReplyServer::~KQOAuthAuthReplyServer() { delete d_ptr; } pumpa-0.9.2/src/kQOAuth/kqoauthmanager_p.h0000644000175000017500000000515012653206625017150 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #ifndef KQOAUTHMANAGER_P_H #define KQOAUTHMANAGER_P_H #include "kqoauthauthreplyserver.h" #include "kqoauthrequest.h" class KQOAUTH_EXPORT KQOAuthManagerPrivate { public: KQOAuthManagerPrivate(KQOAuthManager *parent); ~KQOAuthManagerPrivate(); QList< QPair > createQueryParams(const KQOAuthParameters &requestParams); QMultiMap createTokensFromResponse(QByteArray reply); bool setSuccessfulRequestToken(const QMultiMap &request); bool setSuccessfulAuthorized(const QMultiMap &request); void emitTokens(); bool setupCallbackServer(); KQOAuthManager::KQOAuthError error; KQOAuthRequest *r; // This request is used to cache the user sent request. KQOAuthRequest *opaqueRequest; // This request is used to creating opaque convenience requests for the user. KQOAuthManager * const q_ptr; /** * The items below are needed in order to store the state of the manager and * by that be able to do convenience operations for the user. */ KQOAuthRequest::RequestType currentRequestType; // Variables we store here for opaque request handling. // NOTE: The variables are labeled the same for both access token request // and protected resource access. QString requestToken; QString requestTokenSecret; QString consumerKey; QString consumerKeySecret; QString requestVerifier; KQOAuthAuthReplyServer *callbackServer; bool hasTemporaryToken; bool isVerified; bool isAuthorized; bool autoAuth; QNetworkAccessManager *networkManager; bool managerUserSet; QMap requestIds; QMap requestMap; Q_DECLARE_PUBLIC(KQOAuthManager); }; #endif // KQOAUTHMANAGER_P_H pumpa-0.9.2/src/kQOAuth/kqoauthrequest_1.h0000644000175000017500000000200712653206625017125 0ustar matsmats/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@gmail.com) * http://www.johanpaul.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * KQOAuth 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #ifndef KQOAUTHREQUEST_1_H #define KQOAUTHREQUEST_1_H #include "kqoauthrequest.h" class KQOAUTH_EXPORT KQOAuthRequest_1 : public KQOAuthRequest { public: KQOAuthRequest_1(); }; #endif // KQOAUTHREQUEST_1_H pumpa-0.9.2/src/objectlistwidget.cpp0000644000175000017500000000407612653206625016215 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "objectlistwidget.h" #include "pumpa_defines.h" #include "activitywidget.h" #include //------------------------------------------------------------------------------ ObjectListWidget::ObjectListWidget(QWidget* parent) : ASWidget(parent) {} //------------------------------------------------------------------------------ QASAbstractObjectList* ObjectListWidget::initList(QString endpoint, QObject* parent) { m_asMode = QAS_OBJECTLIST; return QASObjectList::initObjectList(endpoint, parent); } //------------------------------------------------------------------------------ void ObjectListWidget::update() { ASWidget::update(); fetchOlder(200); } //------------------------------------------------------------------------------ ObjectWidgetWithSignals* ObjectListWidget::createWidget(QASAbstractObject* aObj) { QASObject* obj = qobject_cast(aObj); if (!obj) { qDebug() << "ERROR ObjectListWidget::createWidget passed non-object"; return NULL; } ObjectWidget* ow = new ObjectWidget(obj, this); connect(ow, SIGNAL(showContext(QASObject*)), this, SIGNAL(showContext(QASObject*))); return ow; } //------------------------------------------------------------------------------ QASObjectList* ObjectListWidget::objectList() const { return qobject_cast(m_list); } pumpa-0.9.2/src/pumpa_defines.h0000644000175000017500000000535212653206625015131 0ustar matsmats/* Copyright 2013-2016 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _PUMPA_DEFINES_H_ #define _PUMPA_DEFINES_H_ #define CLIENT_NAME "pumpa" #define CLIENT_FANCY_NAME "Pumpa" #define CLIENT_VERSION "0.9.2" #define CLIENT_ICON ":/images/pumpa.png" #define WEBSITE_URL "https://pumpa.branchable.com/" #define MARKUP_DOC_URL "https://pumpa.branchable.com/markdown/" #define BUGTRACKER_URL "https://pumpa.branchable.com/bugs/" #define IMAGE_MAX_WIDTH 320 #define IMAGE_MAX_HEIGHT 320 #define FEED_INBOX 8 #define FEED_MENTIONS 4 #define FEED_DIRECT 2 #define FEED_MEANWHILE 1 #define RECIPIENT_EMPTY 0 #define RECIPIENT_PUBLIC 1 #define RECIPIENT_FOLLOWERS 2 #define PUBLIC_RECIPIENT_ID "http://activityschema.org/collection/public" //------------------------------------------------------------------------------ // First byte is used to tell the slot receiving the network reply how // to interpret the response. (Most are just what activitystreams // class to hand it off to, or some simple action to perform at once). #define QAS_NULL 0 #define QAS_COLLECTION 1 #define QAS_ACTIVITY 2 #define QAS_OBJECTLIST 3 #define QAS_OBJECT 4 #define QAS_ACTORLIST 5 // #define QAS_ACTOR 6 #define QAS_SELF_PROFILE 7 #define QAS_IMAGE_UPLOAD 8 #define QAS_IMAGE_UPDATE 9 #define QAS_SELF_LISTS 10 #define QAS_EDIT_PROFILE 11 // The higher bits can be used for info for the method that is // handling the further processing. #define QAS_NEWER (1 << 8) #define QAS_OLDER (1 << 9) #define QAS_REFRESH (1 << 10) #define QAS_TOGGLE_LIKE (1 << 11) #define QAS_FOLLOW (1 << 12) #define QAS_UNFOLLOW (1 << 13) #define QAS_POST (1 << 14) #define QAS_UPDATE_ONLY (1 << 15) #define QAS_AVATAR_UPLOAD (1 << 16) //------------------------------------------------------------------------------ #define MAX_WORD_LENGTH 40 #define MAX_SUGGESTIONS 10 //------------------------------------------------------------------------------ #endif /* _PUMPA_DEFINES_H_ */ pumpa-0.9.2/src/richtextlabel.h0000644000175000017500000000226612653206625015145 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _RICHTEXTLABEL_H_ #define _RICHTEXTLABEL_H_ #include #include #include #include #include "qactivitystreams.h" #include "filedownloader.h" //------------------------------------------------------------------------------ class RichTextLabel : public QLabel { Q_OBJECT public: RichTextLabel(QWidget* parent = 0, bool singleLine = false); virtual void resizeEvent(QResizeEvent*); private: bool m_singleLine; }; #endif /* _RICHTEXTLABEL_H_ */ pumpa-0.9.2/src/tabwidget.cpp0000644000175000017500000000613312653206625014615 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "tabwidget.h" #include //------------------------------------------------------------------------------ TabWidget::TabWidget(QWidget* parent) : QTabWidget(parent) { m_sMap = new QSignalMapper(this); connect(m_sMap, SIGNAL(mapped(int)), this, SLOT(highlightTab(int))); setTabsClosable(true); connect(tabBar(), SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int))); } //------------------------------------------------------------------------------ int TabWidget::addTab(QWidget* page, const QString& label, bool highlight, bool closable) { int index = QTabWidget::addTab(page, label); if (highlight) addHighlightConnection(page, index); if (!closable) { tabBar()->setTabButton(index, QTabBar::LeftSide, 0); tabBar()->setTabButton(index, QTabBar::RightSide, 0); } else { m_okToClose.insert(index); } return index; } //------------------------------------------------------------------------------ QWidget* TabWidget::closeCurrentTab() { QWidget* w = currentWidget(); closeTab(currentIndex()); return w; } //------------------------------------------------------------------------------ void TabWidget::closeTab(int index) { if (!closable(index)) { qDebug() << "[ERROR] Tried to close unclosable tab" << index; return; } removeTab(index); } //------------------------------------------------------------------------------ void TabWidget::highlightTab(int index) { if (index == -1) index = currentIndex(); tabBar()->setTabTextColor(index, Qt::red); } //------------------------------------------------------------------------------ void TabWidget::deHighlightTab(int index) { if (index == -1) index = currentIndex(); QPalette pal; tabBar()->setTabTextColor(index, pal.color(foregroundRole())); } //------------------------------------------------------------------------------ void TabWidget::addHighlightConnection(QWidget* page, int index) { m_sMap->setMapping(page, index); connect(page, SIGNAL(highlightMe()), m_sMap, SLOT(map())); } //------------------------------------------------------------------------------ void TabWidget::keyPressEvent(QKeyEvent* event) { int keyn = event->key() - Qt::Key_0; if ((event->modifiers() & Qt::AltModifier) && // alt (keyn >= 1 && keyn <= 9 && keyn <= count())) { // + 0 .. 9 setCurrentIndex(keyn-1); } else { QTabWidget::keyPressEvent(event); } } pumpa-0.9.2/src/qasabstractobject.h0000644000175000017500000000503412653206625016006 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _QASABSTRACTOBJECT_H_ #define _QASABSTRACTOBJECT_H_ #include #include #include #include "pumpa_defines.h" #include "json.h" //------------------------------------------------------------------------------ class QASAbstractObject : public QObject { Q_OBJECT public: // virtual void refresh(); virtual QString apiLink() const { return ""; } int asType() const { return m_asType; } virtual bool isDeleted() const { return false; } QDateTime lastRefreshed() const { return m_lastRefreshed; } void lastRefreshed(QDateTime dt) { m_lastRefreshed = dt; } QVariantMap json() const { return m_json; } signals: void changed(); // void request(QString, int); protected: QASAbstractObject(int asType, QObject* parent); virtual void connectSignals(QASAbstractObject* obj, bool changed=true, bool req=true); static qint64 sortIntByDateTime(QDateTime dt); public: static void updateVar(QVariantMap, QString&, QString, bool&); static void updateVar(QVariantMap, bool&, QString, bool&); static void updateVar(QVariantMap, double&, QString, bool&); static void updateVar(QVariantMap, qulonglong&, QString, bool&, bool ignoreDecrease=false); static void updateVar(QVariantMap, QDateTime&, QString, bool&); static void updateVar(QVariantMap, QString&, QString, QString, bool&); static void updateVar(QVariantMap, bool&, QString, QString, bool&); static void updateVar(QVariantMap, double&, QString, QString, bool&); static void updateVar(QVariantMap, QString&, QString, QString, QString, bool&); static void addVar(QVariantMap&, QString, QString); static void updateUrlOrProxy(QVariantMap, QString&, bool&); protected: QDateTime m_lastRefreshed; int m_asType; QVariantMap m_json; }; #endif /* _QASABSTRACTOBJECT_H_ */ pumpa-0.9.2/src/pumpapp.h0000644000175000017500000001734212653206625013776 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _PUMPAPP_H_ #define _PUMPAPP_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_DBUS #include #endif #include "QtKOAuth" #include "pumpa_defines.h" #include "qactivitystreams.h" #include "collectionwidget.h" #include "oauthwizard.h" #include "tabwidget.h" #include "pumpasettingsdialog.h" #include "pumpasettings.h" #include "contextwidget.h" #include "objectlistwidget.h" #include "messagewindow.h" #include "filedownloader.h" #include "editprofiledialog.h" //------------------------------------------------------------------------------ class PumpApp : public QMainWindow { Q_OBJECT public: PumpApp(PumpaSettings* settings, QString locale="", QWidget* parent=0); virtual ~PumpApp(); signals: void userAuthorizationStarted(); private slots: void onSslErrors(QNetworkReply*, QList); void userTestDoneAndFollow(); void trayIconActivated(QSystemTrayIcon::ActivationReason reason); void updateTrayIcon(); void toggleVisible(); void timelineHighlighted(int); void onNewMinorObjects(); void followDialog(); void editProfile(); void editProfileDialog(); void onLike(QASObject* obj); void onShare(QASObject* obj); void postNote(QString note, QString title, RecipientList to, RecipientList cc); void postImage(QString msg, QString title, QString imageFile, RecipientList to, RecipientList cc); void postAvatarImage(QString imageFile); void postReply(QASObject* replyToObj, QString content, RecipientList to, RecipientList cc); void postEdit(QASObject* obj, QString content, QString title); void follow(QString acctId, bool follow); void onDeleteObject(QASObject* obj); void onEditObject(QASObject* obj); void onProfileEdited(QASActor* profile, QString newImageFile); void errorMessage(QString msg); void notifyMessage(QString msg); void statusMessage(const QString& msg); void onShowContext(QASObject*); void tabSelected(int index); void onClientRegistered(QString, QString, QString, QString); void onAccessTokenReceived(QString token, QString tokenSecret); void onAuthorizedRequestReady(QByteArray response, int id); void uploadProgress(qint64 bytesSent, qint64 bytesTotal); void uploadCanceled(bool abortRequest=true); void request(QString endpoint, int response_id, KQOAuthRequest::RequestHttpMethod method = KQOAuthRequest::GET, QVariantMap data=QVariantMap()); void exit(); void about(); void reportBug(); void preferences(); void newNote(QASObject* obj = NULL, QASObjectList* to = NULL, QASObjectList* cc = NULL, bool edit = false); void reload(); void loadOlder(); void startPumping(); void launchOAuthWizard(); void wizardCancelled(); void debugAction(); void showFollowers(); void showFollowing(); void showFavourites(); void showUserActivities(); void showFirehose(); void closeTab(); protected: void timerEvent(QTimerEvent*); virtual bool event(QEvent* e) { if (e->type() == QEvent::WindowActivate) resetNotifications(); return QMainWindow::event(e); } void closeEvent(QCloseEvent* e) { m_showHideAction->setText(showHideText(false)); QMainWindow::closeEvent(e); } private: void addPublicRecipient(RecipientList& rl); void uploadProfile(); bool tabShown(ASWidget* aw) const; bool isShown(QASAbstractObject* obj); KQOAuthRequest* initRequest(QString endpoint, KQOAuthRequest::RequestHttpMethod method); QNetworkReply* executeRequest(KQOAuthRequest* request, int response_id); typedef QPair requestInfo_t; QMap m_requestMap; int m_nextRequestId; void setLoading(bool on); void refreshObject(QASAbstractObject* obj); void uploadFile(QString filename, int flags=0); void updatePostedImage(QVariantMap obj, int flags=0); void postImageActivity(QVariantMap obj, int flags=0); void errorBox(QString msg); bool webFingerFromString(QString text, QString& username, QString& server); void testUserAndFollow(QString username, QString server); QString apiUrl(QString endpoint); // constructs api/user/$username/$path QString apiUser(QString path); void addRecipient(QVariantMap& data, QString name, RecipientList to); void resetNotifications(); void createTrayIcon(); QString showHideText(bool); QString showHideText() { return showHideText(isVisible()); } void connectCollection(ASWidget* w, bool highlight=true); bool haveOAuth(); void resetTimer(); void refreshTimeLabels(); void fetchAll(bool); QString inboxEndpoint(QString path); void feed(QString verb, QVariantMap object, int response_id, RecipientList to = RecipientList(), RecipientList cc = RecipientList()); bool sendNotification(QString summary, QString text); PumpaSettingsDialog* m_settingsDialog; PumpaSettings* m_s; void followActor(QASActor* actor, bool doFollow=true); void addCompletion(QString from, QString to, bool add); void createActions(); void createMenu(); QAction* newNoteAction; // QAction* newPictureAction; QAction* reloadAction; QAction* followAction; QAction* profileAction; QAction* loadOlderAction; QAction* openPrefsAction; QAction* exitAction; QMenu* fileMenu; QAction* aboutAction; QAction* aboutQtAction; QAction* reportBugAction; QMenu* helpMenu; QAction* m_followersAction; QAction* m_followingAction; QAction* m_favouritesAction; QAction* m_userActivitiesAction; QAction* m_firehoseAction; QAction* m_closeTabAction; QMenu* m_tabsMenu; QAction* m_debugAction; KQOAuthManager *m_oam; FileDownloadManager* m_fdm; TabWidget* m_tabWidget; CollectionWidget* m_inboxWidget; CollectionWidget* m_directMajorWidget; CollectionWidget* m_directMinorWidget; CollectionWidget* m_inboxMinorWidget; CollectionWidget* m_firehoseWidget; QList m_contextWidgets; ObjectListWidget* m_followersWidget; ObjectListWidget* m_followingWidget; ObjectListWidget* m_favouritesWidget; CollectionWidget* m_userActivitiesWidget; QLabel* m_loadIcon; QMovie* m_loadMovie; bool m_isLoading; QASActor* m_selfActor; OAuthWizard* m_wiz; MessageWindow* m_messageWindow; EditProfileDialog* m_editProfileDialog; QSystemTrayIcon* m_trayIcon; QMenu* m_trayIconMenu; QAction* m_showHideAction; QString m_locale; int m_timerId; int m_timerCount; QVariantMap m_imageObject; RecipientList m_imageTo; RecipientList m_imageCc; QVariantMap m_profile; RecipientList m_recipientLists; MessageEdit::completion_t m_completions; QProgressDialog* m_uploadDialog; QNetworkReply* m_uploadRequest; QNetworkAccessManager* m_nam; QSignalMapper* m_notifyMap; #ifdef USE_DBUS QDBusInterface* m_dbus; #endif }; #endif /* _PUMPAPP_H_ */ pumpa-0.9.2/src/messageedit.cpp0000644000175000017500000001436312653206625015141 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "messageedit.h" #include "pumpa_defines.h" #include #include #include #include //------------------------------------------------------------------------------ MessageEdit::MessageEdit(const PumpaSettings* s, QWidget* parent) : QTextEdit(parent), m_completions(NULL), m_checker(NULL), m_s(s) { setAcceptRichText(false); #ifdef USE_ASPELL m_checker = new QASpell(this); #endif m_highlighter = new FancyHighlighter(document(), m_checker); m_completer = new QCompleter(this); m_completer->setWidget(this); m_completer->setCaseSensitivity(Qt::CaseInsensitive); m_completer->setCompletionMode(QCompleter::PopupCompletion); m_completer->setModelSorting(QCompleter::UnsortedModel); m_completer->setMaxVisibleItems(10); m_model = new QStringListModel(this); m_completer->setModel(m_model); connect(m_completer, SIGNAL(activated(QString)), this, SLOT(insertCompletion(QString))); } //------------------------------------------------------------------------------ void MessageEdit::setCompletions(const completion_t* completions) { m_completions = completions; m_model->setStringList(m_completions->keys()); } //------------------------------------------------------------------------------ void MessageEdit::hideCompletion() { if (m_completer) m_completer->popup()->hide(); } //------------------------------------------------------------------------------ // completion code partially from here: // http://qt-project.org/forums/viewthread/5376 void MessageEdit::keyPressEvent(QKeyEvent* event) { int key = event->key(); int mods = event->modifiers(); QAbstractItemView* popup = m_completer->popup(); if (popup->isVisible()) { switch (key) { case Qt::Key_Enter: case Qt::Key_Return: case Qt::Key_Tab: case Qt::Key_Escape: hideCompletion(); event->ignore(); return; } } if (key == Qt::Key_Return && (mods & Qt::ControlModifier)) { emit ready(); return; } QTextEdit::keyPressEvent(event); const QString completionPrefix = wordAtCursor(); if (completionPrefix != m_completer->completionPrefix()) { m_completer->setCompletionPrefix(completionPrefix); QModelIndex idx = m_completer->completionModel()->index(0, 0); popup->setCurrentIndex(idx); } if (!event->text().isEmpty() && completionPrefix.length() >= 2) { QRect r = cursorRect(); r.setWidth(popup->sizeHintForColumn(0) + popup->verticalScrollBar()->sizeHint().width()); r.setHeight(popup->sizeHintForRow(0)); m_model->setStringList(m_completions->keys()); m_completer->complete(r); } else { hideCompletion(); } } //------------------------------------------------------------------------------ void MessageEdit::insertCompletion(QString completion) { if (m_completer->widget() != this) return; // qDebug() << "insertCompletion" << completion; hideCompletion(); QTextCursor tc = textCursor(); tc.select(QTextCursor::WordUnderCursor); tc.removeSelectedText(); tc.deletePreviousChar(); QASActor* actor = m_completions->value(completion); QString newText = m_s->useMarkdown() ? QString("[@%1](%2)").arg(actor->displayNameOrWebFinger()).arg(actor->url()): QString("@%1").arg(actor->displayNameOrWebFinger()); tc.insertText(newText + " "); setTextCursor(tc); emit addRecipient(actor); } //------------------------------------------------------------------------------ QString MessageEdit::wordAtCursor() const { QTextCursor tc = textCursor(); tc.select(QTextCursor::WordUnderCursor); if (tc.document()->characterAt(tc.selectionStart()-1) == '@') return tc.selectedText(); return ""; } //------------------------------------------------------------------------------ void MessageEdit::focusInEvent(QFocusEvent *event) { if (m_completer) m_completer->setWidget(this); QTextEdit::focusInEvent(event); } //------------------------------------------------------------------------------ void MessageEdit::contextMenuEvent(QContextMenuEvent* event) { QMenu* menu = createStandardContextMenu(); #ifdef USE_ASPELL m_contextCursor = cursorForPosition(event->pos()); m_contextCursor.select(QTextCursor::WordUnderCursor); QTextDocument* doc = m_contextCursor.document(); QRegExp rx("[\\w']"); int pos = m_contextCursor.selectionEnd(); while (rx.exactMatch(doc->characterAt(pos))) { pos++; m_contextCursor.setPosition(pos, QTextCursor::KeepAnchor); } QString word = m_contextCursor.selectedText(); if (word.endsWith("'")) word.remove(word.size()-1, 1); if (!word.isEmpty() && m_checker && !m_checker->checkWord(word)) { QStringList suggestions = m_checker->suggestions(word); if (!suggestions.empty()) { QMenu* m = menu->addMenu(tr("Spelling suggestions...")); m_sMapper = new QSignalMapper(this); connect(m_sMapper, SIGNAL(mapped(QString)), this, SLOT(replaceSuggestion(QString))); for (int i=0; iaddAction(sWord); connect(act, SIGNAL(triggered()), m_sMapper, SLOT(map())); m_sMapper->setMapping(act, sWord); } } } #endif menu->exec(event->globalPos()); delete menu; } //------------------------------------------------------------------------------ void MessageEdit::replaceSuggestion(const QString& word) { if (m_contextCursor.isNull() || word.isEmpty()) return; m_contextCursor.removeSelectedText(); m_contextCursor.insertText(word); m_contextCursor = QTextCursor(); } pumpa-0.9.2/src/contextwidget.h0000644000175000017500000000225212653206625015176 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _CONTEXTWIDGET_H_ #define _CONTEXTWIDGET_H_ #include "qactivitystreams.h" #include "aswidget.h" //------------------------------------------------------------------------------ class ContextWidget : public ASWidget { Q_OBJECT public: ContextWidget(QWidget* parent); void setObject(QASObject* obj); virtual void fetchNewer(); protected: virtual void update(); bool updateNumReplies(); int m_numReplies; QASObject* m_object; }; #endif /* _CONTEXTWIDGET_H_ */ pumpa-0.9.2/src/messageedit.h0000644000175000017500000000363112653206625014602 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef MESSAGE_EDIT_H #define MESSAGE_EDIT_H #include #include #include #include #include "qasactor.h" #include "qaspell.h" #include "fancyhighlighter.h" #include "pumpasettings.h" //------------------------------------------------------------------------------ class MessageEdit : public QTextEdit { Q_OBJECT public: MessageEdit(const PumpaSettings* s, QWidget* parent=0); typedef QMap completion_t; void setCompletions(const completion_t* completions); void hideCompletion(); const completion_t* getCompletions() { return m_completions; } signals: void ready(); void addRecipient(QASActor*); protected slots: void insertCompletion(QString); void replaceSuggestion(const QString& word); protected: virtual void focusInEvent(QFocusEvent *event); virtual void keyPressEvent(QKeyEvent* event); virtual void contextMenuEvent(QContextMenuEvent* event); QString wordAtCursor() const; FancyHighlighter* m_highlighter; QCompleter* m_completer; QStringListModel* m_model; const completion_t* m_completions; QSignalMapper* m_sMapper; QASpell* m_checker; QTextCursor m_contextCursor; const PumpaSettings* m_s; }; #endif pumpa-0.9.2/src/Makefile0000644000175000017500000000002412653206625013570 0ustar matsmatsall: $(MAKE) -C .. pumpa-0.9.2/src/messagewindow.h0000644000175000017500000000734612653206625015173 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef MESSAGE_WINDOW_H #define MESSAGE_WINDOW_H #include #include #include #include #include #include #include #include #include #include #include #include "messageedit.h" #include "qactivitystreams.h" #include "pumpasettings.h" #include "texttoolbutton.h" #include "richtextlabel.h" #include "messagerecipients.h" //------------------------------------------------------------------------------ class PictureLabel : public QLabel { Q_OBJECT public: PictureLabel(QWidget* parent); void setOriginalPixmap(QPixmap); protected: virtual void resizeEvent(QResizeEvent* event); QPixmap m_originalPixmap; }; //------------------------------------------------------------------------------ class MessageWindow : public QDialog { Q_OBJECT public: MessageWindow(PumpaSettings* s, const RecipientList* rl, QWidget* parent=0); virtual void accept(); void newMessage(QASObject* obj, QASObjectList* to, QASObjectList* cc); void editMessage(QASObject* obj); void clear(); void setCompletions(const MessageEdit::completion_t* completions); protected: virtual void showEvent(QShowEvent*); signals: void sendMessage(QString, QString, RecipientList, RecipientList); void sendImage(QString, QString, QString, RecipientList, RecipientList); void sendReply(QASObject*, QString, RecipientList, RecipientList); void sendEdit(QASObject*, QString, QString); private slots: void onAddPicture(); void onRemovePicture(); void togglePreview(); void updatePreview(bool force=false); void onAddRecipient(QASActor*); void onAddTo(); void onAddCc(); void onMarkdownChecked(int); private: void initWindow(QString title, QString buttonText, bool showRecipients); void addRecipientWindow(MessageRecipients*, QString); void updateAddPicture(); void copyRecipients(MessageRecipients*, QASObjectList*); void setDefaultRecipients(MessageRecipients*, int); void addToRecipientList(QString, QASObject*); QVBoxLayout* m_layout; QLabel* m_infoLabel; QLabel* m_markupLabel; QHBoxLayout* m_infoLayout; QCheckBox* m_markdownCheckBox; QFormLayout* m_addressLayout; MessageEdit* m_textEdit; QHBoxLayout* m_buttonLayout; QHBoxLayout* m_pictureButtonLayout; TextToolButton* m_addPictureButton; TextToolButton* m_removePictureButton; TextToolButton* m_addToButton; TextToolButton* m_addCcButton; QLabel* m_toLabel; QLabel* m_ccLabel; QLabel* m_charCountLabel; RichTextLabel* m_previewLabel; QScrollArea* m_previewArea; QSplitter* m_splitter; PictureLabel* m_pictureLabel; QLineEdit* m_title; QPushButton* m_cancelButton; QPushButton* m_sendButton; QPushButton* m_previewButton; QString m_imageFileName; MessageRecipients* m_toRecipients; MessageRecipients* m_ccRecipients; QMap m_recipientSelection; QStringList m_recipientList; bool m_editing; bool m_isReply; QASObject* m_obj; PumpaSettings* m_s; const RecipientList* m_rl; }; #endif pumpa-0.9.2/src/objectwidget.h0000644000175000017500000000426012653206625014761 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _OBJECTWIDGET_H_ #define _OBJECTWIDGET_H_ #include #include #include "qactivitystreams.h" #include "shortobjectwidget.h" #include "fullobjectwidget.h" #include "texttoolbutton.h" #include "richtextlabel.h" #include "objectwidgetwithsignals.h" //------------------------------------------------------------------------------ class ObjectWidget : public ObjectWidgetWithSignals { Q_OBJECT public: ObjectWidget(QASObject* obj, QWidget* parent = 0); virtual ~ObjectWidget(); virtual void changeObject(QASAbstractObject* obj, bool fullObject); virtual void changeObject(QASAbstractObject* obj) { changeObject(obj, true); } QASObject* object() const { return m_object; } virtual QASAbstractObject* asObject() const { return object(); } virtual void refreshTimeLabels(); void disableLessButton(); void setActivity(QASActivity* a) { if (m_objectWidget) m_objectWidget->setActivity(a); } signals: void moreClicked(); void lessClicked(); void showContext(QASObject*); private slots: void showMore(); void showLess(); void onChanged(); void updateContextLabel(); void onShowContext(); private: FullObjectWidget* m_objectWidget; ShortObjectWidget* m_shortObjectWidget; RichTextLabel* m_contextLabel; TextToolButton* m_contextButton; QVBoxLayout* m_layout; QHBoxLayout* m_topLayout; QASObject* m_object; QASObject* m_irtObject; QASActivity* m_activity; bool m_short; }; #endif /* _OBJECTWIDGET_H_ */ pumpa-0.9.2/src/qasabstractobjectlist.h0000644000175000017500000000444012653206625016702 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _QASABSTRACTOBJECTLIST_H_ #define _QASABSTRACTOBJECTLIST_H_ #include "qasabstractobject.h" #include //------------------------------------------------------------------------------ class QASAbstractObjectList : public QASAbstractObject { Q_OBJECT protected: QASAbstractObjectList(int asType, QString url, QObject* parent); public: virtual void update(QVariantMap json, bool older, bool updateOnly=false); QString prevLink() const { return m_prevLink.isEmpty() ? m_url : m_prevLink; } QString nextLink() const { return m_nextLink; } size_t size() const { return m_items.size(); } QASAbstractObject* at(size_t i) const { if (i >= size()) return NULL; return m_items[i]; } qulonglong totalItems() const { return m_totalItems; } QString url() const { return m_url; } QString urlOrProxy() const; virtual QString apiLink() const { return urlOrProxy(); } bool hasMore() const { return m_hasMore; } void addObject(QASAbstractObject*); void removeObject(QASAbstractObject*, bool signal=true); bool contains(QASAbstractObject* obj) const { return m_item_set.contains(obj); } bool firstTime() const { return m_firstTime; } protected: virtual QASAbstractObject* getAbstractObject(QVariantMap json, QObject* parent) = 0; QString m_displayName; QString m_url; qulonglong m_totalItems; QString m_proxyUrl; bool m_hasMore; QList m_items; QSet m_item_set; QString m_prevLink, m_nextLink; bool m_firstTime; }; #endif /* _QASABSTRACTOBJECTLIST_H_ */ pumpa-0.9.2/src/texttoolbutton.h0000644000175000017500000000174012653206625015425 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _TEXTTOOLBUTTON_H_ #define _TEXTTOOLBUTTON_H_ #include class TextToolButton : public QToolButton { Q_OBJECT public: TextToolButton(QWidget* parent=0); TextToolButton(QString text, QWidget* parent=0); private: void setup(); }; #endif /* _TEXTTOOLBUTTON_H_ */ pumpa-0.9.2/src/imagelabel.cpp0000644000175000017500000000222612653206625014724 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "imagelabel.h" #include "pumpa_defines.h" //------------------------------------------------------------------------------ ImageLabel::ImageLabel(QWidget* parent) : QLabel(parent) { setMaximumSize(IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT); setFocusPolicy(Qt::NoFocus); } //------------------------------------------------------------------------------ void ImageLabel::mousePressEvent(QMouseEvent* event) { QLabel::mousePressEvent(event); emit clicked(); } pumpa-0.9.2/src/qascollection.cpp0000644000175000017500000000462112653206625015503 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "qascollection.h" #include "util.h" #include //------------------------------------------------------------------------------ QMap QASCollection::s_collections; void QASCollection::clearCache() { deleteMap(s_collections); } //------------------------------------------------------------------------------ QASCollection::QASCollection(QString url, QObject* parent) : QASAbstractObjectList(QAS_COLLECTION, url, parent) { #ifdef DEBUG_QAS qDebug() << "new Collection" << m_url; #endif } //------------------------------------------------------------------------------ QASAbstractObject* QASCollection::getAbstractObject(QVariantMap json, QObject* parent) { return QASActivity::getActivity(json, parent); } //------------------------------------------------------------------------------ QASCollection* QASCollection::getCollection(QVariantMap json, QObject* parent, int id) { QString url = json["url"].toString(); if (url.isEmpty()) url = json["id"].toString(); // if (url.isEmpty()) // return NULL; QASCollection* coll = s_collections.contains(url) ? s_collections[url] : new QASCollection(url, parent); s_collections.insert(url, coll); coll->update(json, id & QAS_OLDER, id & QAS_UPDATE_ONLY); return coll; } //------------------------------------------------------------------------------ QASCollection* QASCollection::initCollection(QString url, QObject* parent) { if (s_collections.contains(url)) return s_collections[url]; QASCollection* coll = new QASCollection(url, parent); s_collections.insert(url, coll); return coll; } pumpa-0.9.2/src/util.cpp0000644000175000017500000002674412653206625013632 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "util.h" #include #include #include #include #include #include #ifdef DEBUG_MEMORY #include #include #endif #include "sundown/markdown.h" #include "sundown/html.h" #include "sundown/buffer.h" #ifdef USE_TIDY_TIDY #include #include #endif #ifdef USE_TIDY #include #include #endif //------------------------------------------------------------------------------ QString markDown(QString text) { struct sd_callbacks callbacks; struct html_renderopt options; sdhtml_renderer(&callbacks, &options, 0); struct sd_markdown* markdown = sd_markdown_new(0, 16, &callbacks, &options); struct buf* ob = bufnew(64); // QByteArray ba = text.toLocal8Bit(); QByteArray ba = text.toUtf8(); sd_markdown_render(ob, (const unsigned char*)ba.constData(), ba.size(), markdown); sd_markdown_free(markdown); // QString ret = QString::fromLocal8Bit((char*)ob->data, ob->size); QString ret = QString::fromUtf8((char*)ob->data, ob->size); bufrelease(ob); return ret.trimmed(); } //------------------------------------------------------------------------------ QString siteUrlFixer(QString url, bool useSsl) { if (!url.startsWith("http://") && !url.startsWith("https://")) url = (useSsl ? "https://" : "http://") + url; if (url.endsWith('/')) url.chop(1); return url; } //------------------------------------------------------------------------------ QString linkifyUrls(QString text, bool useMarkdown) { QRegExp rx(QString("(^|\\s)%1([\\s\\.\\,\\!\\?\\)]|$)").arg(URL_REGEX_STRICT)); QStringList lines = text.split('\n'); for (int i=0; i%3").arg(before).arg(url).arg(after) : QString("%1%2%3").arg(before).arg(url).arg(after); line.replace(pos, len, newText); pos += newText.count(); } lines[i] = line; } return lines.join("\n"); } //------------------------------------------------------------------------------ QString changePairedTags(QString text, QString begin, QString end, QString newBegin, QString newEnd, QString nogoItems) { QRegExp rx(QString(MD_PAIR_REGEX).arg(begin).arg(end).arg(nogoItems)); int pos = 0; while ((pos = rx.indexIn(text, pos)) != -1) { int len = rx.matchedLength(); QString newText = QString(newBegin + rx.cap(1) + newEnd); text.replace(pos, len, newText); pos += newText.count(); } return text; } //------------------------------------------------------------------------------ QString siteUrlToAccountId(QString username, QString url) { if (url.startsWith("http://")) url.remove(0, 7); if (url.startsWith("https://")) url.remove(0, 8); if (url.endsWith('/')) url.chop(1); return username + "@" + url; } //------------------------------------------------------------------------------ QString relativeFuzzyTime(QDateTime sTime, bool longTime) { QString dateStr = sTime.toString("ddd d MMMM yyyy"); int secs = sTime.secsTo(QDateTime::currentDateTime().toUTC()); if (secs < 0) secs = 0; float t = (float)secs; t /= 60; int mins = qRound(t); t /= 60; int hours = qRound(t); t /= 24; int days = qRound(t); t /= 7; int weeks = qRound(t); t /= 4.33; int months = qRound(t); t /= 12; int years = qRound(t); if (secs < 60) { dateStr = QObject::tr("a few seconds ago"); } else if (mins == 1) { dateStr = QObject::tr("one minute ago"); } else if (hours < 1) { dateStr = QObject::tr("%n minutes ago", 0, mins); } else if (hours == 1) { dateStr = QObject::tr("one hour ago", 0, hours); } else if (days < 1) { dateStr = QObject::tr("%n hours ago", 0, hours); } else if (longTime) { if (days == 1) { dateStr = QObject::tr("one day ago", 0, days); } else if (weeks < 1) { dateStr = QObject::tr("%n days ago", 0, days); } else if (weeks == 1) { dateStr = QObject::tr("one week ago", 0, weeks); } else if (months < 1) { dateStr = QObject::tr("%n weeks ago", 0, weeks); } else if (months == 1) { dateStr = QObject::tr("one month ago", 0, months); } else if (years < 1) { dateStr = QObject::tr("%n months ago", 0, months); } else if (years == 1) { dateStr = QObject::tr("one year ago", 0, years); } else { dateStr = QObject::tr("%n years ago", 0, years); } } return dateStr; } //------------------------------------------------------------------------------ bool splitWebfingerId(QString accountId, QString& username, QString& server) { static QRegExp rx("^([\\w\\._-+]+)@([\\w\\._-+]+)$"); if (!rx.exactMatch(accountId.trimmed())) return false; username = rx.cap(1); server = rx.cap(2); return true; } //------------------------------------------------------------------------------ long getMaxRSS() { #ifdef DEBUG_MEMORY struct rusage rusage; getrusage(RUSAGE_SELF, &rusage); return rusage.ru_maxrss; #else return 0; #endif } //------------------------------------------------------------------------------ long getCurrentRSS() { #ifdef DEBUG_MEMORY QFile fp("/proc/self/statm"); if (!fp.open(QIODevice::ReadOnly)) return -1; QTextStream in(&fp); QString line = in.readLine(); QStringList parts = line.split(" "); return parts[1].toLong() * sysconf( _SC_PAGESIZE); #else return 0; #endif } //------------------------------------------------------------------------------ void checkMemory(QString desc) { static long oldMem = -1; long mem = getCurrentRSS(); long diff = 0; if (oldMem > 0) diff = mem-oldMem; QString msg("RESIDENT MEMORY"); if (!desc.isEmpty()) msg += " (" + desc + ")"; msg += QString(": %1 KB").arg((float)mem/1024.0, 0, 'f', 2); if (diff != 0) msg += QString(" (%2%1)").arg(diff).arg(diff > 0 ? '+' : '-'); qDebug() << msg; } //------------------------------------------------------------------------------ QString removeHtml(QString origText) { QString text = origText; // Remove any inline HTML tags // text.replace(QRegExp(HTML_TAG_REGEX), "<\\1>"); QRegExp rx(HTML_TAG_REGEX); QRegExp urlRx(URL_REGEX); int pos = 0; while ((pos = rx.indexIn(text, pos)) != -1) { int len = rx.matchedLength(); QString tag = rx.cap(1); if (urlRx.exactMatch(tag)) { pos += len; } else { QString newText = "<" + tag + ">"; text.replace(pos, len, newText); pos += newText.length(); } } return text; } //------------------------------------------------------------------------------ #ifndef NO_TIDY // Tidy changed TidyBodyOnly type from bool to int at 2007-05-24. // Returns true when a new version (int) is found bool isTidyWithIntBodyOnlyCheck() { QString releaseDateStr(tidyReleaseDate()); int yearPos = releaseDateStr.indexOf(QRegExp(" [0-9]{4}")); if (yearPos != -1) releaseDateStr = releaseDateStr.left(yearPos + 5); QDate releaseDate = QLocale::c().toDate(releaseDateStr, "d MMMM yyyy"); QDate changeDate = QLocale::c().toDate("24 May 2007", "d MMMM yyyy"); bool isNewer = releaseDate > changeDate; #ifdef DEBUG_MARKUP qDebug() << "\n[DEBUG] tidy release date:" << releaseDate.toString(Qt::ISODate); qDebug() << "\n[DEBUG] use API with new TidyBodyOnly as int :" << isNewer; #endif return isNewer; } #endif //------------------------------------------------------------------------------ QString tidyHtml(QString str, bool& ok) { #ifdef NO_TIDY ok = true; return str; #else QString res = str; ok = false; static bool isTidyWithIntBodyOnly = isTidyWithIntBodyOnlyCheck(); TidyDoc tdoc = tidyCreate(); TidyBuffer output; TidyBuffer errbuf; tidyBufInit(&output); tidyBufInit(&errbuf); bool configOk = tidyOptSetBool(tdoc, TidyXhtmlOut, yes) && tidyOptSetBool(tdoc, TidyForceOutput, yes) && tidyOptSetBool(tdoc, TidyMark, no) && (isTidyWithIntBodyOnly ? tidyOptSetInt(tdoc, TidyBodyOnly, 1) : tidyOptSetBool(tdoc, TidyBodyOnly, yes)) && tidyOptSetInt(tdoc, TidyWrapLen, 0) && tidyOptSetInt(tdoc, TidyDoctypeMode, TidyDoctypeOmit); if (configOk && (tidySetCharEncoding(tdoc, "utf8") >= 0) && (tidySetErrorBuffer(tdoc, &errbuf) >= 0) && (tidyParseString(tdoc, str.toUtf8().data()) >= 0) && (tidyCleanAndRepair(tdoc) >= 0) && (tidyRunDiagnostics(tdoc) >= 0) && (tidySaveBuffer(tdoc, &output) >= 0) && (output.bp != 0 && output.size > 0)) { res = QString::fromUtf8((char*)output.bp, output.size); ok = true; } #ifdef DEBUG_MARKUP if (errbuf.size > 0) { QString errStr = QString::fromUtf8((char*)errbuf.bp, errbuf.size); qDebug() << "\n[DEBUG] MARKUP, libtidy errors and warnings:\n" << errStr; } #endif if (output.bp != 0) tidyBufFree(&output); if (errbuf.bp != 0) tidyBufFree(&errbuf); tidyRelease(tdoc); return res.trimmed(); #endif } //------------------------------------------------------------------------------ QString addTextMarkup(QString text, bool useMarkdown) { QString oldText = text; #ifdef DEBUG_MARKUP qDebug() << "\n[DEBUG] MARKUP\n" << text; #endif #ifdef NO_TIDY text = removeHtml(text); # ifdef DEBUG_MARKUP qDebug() << "\n[DEBUG] MARKUP (clean inline HTML)\n" << text; # endif #endif if (useMarkdown) { // apply markdown text = markDown(text); } else { // linkify plain URLs text = linkifyUrls(text, useMarkdown); #ifdef DEBUG_MARKUP qDebug() << "\n[DEBUG] MARKUP (linkify plain URLs)\n" << text; #endif // This is a bit of a hack: if the text doesn't certain tags we // replace newlines with breaks. QRegExp rx("

    |

      ", Qt::CaseInsensitive); if (rx.indexIn(text) == -1) text.replace("\n", "
      "); } #ifdef DEBUG_MARKUP qDebug() << "\n[DEBUG] MARKUP (apply" << (useMarkdown?"Markdown)":"text conversion)") << "\n" << text; #endif #ifndef NO_TIDY bool tidyOk = false; text = tidyHtml(text, tidyOk); #endif #ifdef DEBUG_MARKUP qDebug() << "\n[DEBUG] MARKUP (libtidy)\n" << text; #endif return text; } //------------------------------------------------------------------------------ QString processTitle(QString text, bool removeLtgt) { // text = removeHtml(text); if (removeLtgt) { text.replace("<", "<"); text.replace(">", ">"); } text.replace("\n", " "); return text.trimmed(); } //------------------------------------------------------------------------------ QString slashify(QString path) { QString ret = path; if (!ret.endsWith('/')) ret.append('/'); return ret; } pumpa-0.9.2/src/qasabstractobjectlist.cpp0000644000175000017500000001220412653206625017232 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "qasabstractobjectlist.h" #include "util.h" #include #include //------------------------------------------------------------------------------ QASAbstractObjectList::QASAbstractObjectList(int asType, QString url, QObject* parent) : QASAbstractObject(asType, parent), m_url(url), m_totalItems(0), m_hasMore(false), m_firstTime(true) {} //------------------------------------------------------------------------------ void QASAbstractObjectList::update(QVariantMap json, bool older, bool updateOnly) { #ifdef DEBUG_QAS qDebug() << "updating AbstractObjectList" << m_url; #endif bool ch = false; bool dummy = false; updateVar(json, m_displayName, "displayName", ch); updateVar(json, m_totalItems, "totalItems", ch, true); updateVar(json, m_proxyUrl, "pump_io", "proxyURL", ch); // In pump.io the next link goes "next" in the UI, i.e. to older // stuff, so: // next => older // prev => newer // // If we are loading older stuff, update only the next link. If it // is empty it means we have reached the oldest stuff. // // If we are loading newer stuff, update only the prev link, except // if it is empty, then don't touch it. (That means there's no newer // stuff, but there's bound to be more later :-) // // And a special case is when we load it the first time, then both // next and prev links should be updated. if (!updateOnly) { if (older || m_firstTime) { m_nextLink = ""; // it's left as empty if it doesn't exist in the // json updateVar(json, m_nextLink, "links", "next", "href", dummy); // if (m_url.contains("/inbox/")) // qDebug() << "***" << m_url << "next" << m_nextLink; } if (!older || m_firstTime) { // updateVar doesn't touch it if it is empty in the json updateVar(json, m_prevLink, "links", "prev", "href", dummy); // if (m_url.contains("/inbox/")) // qDebug() << "***" << m_url << "prev" << m_prevLink; } } // Items need to be processed chronologically. We assume that // collections come in as newest first, so we need to start // processing them from the end. // Start adding from the top or bottom, depending on value of older. int mi = older ? m_items.size() : 0; QVariantList items_json = json["items"].toList(); for (int i=items_json.count()-1; i>=0; --i) { QASAbstractObject* obj = getAbstractObject(items_json.at(i).toMap(), parent()); if (!obj || updateOnly || m_item_set.contains(obj)) continue; m_items.insert(mi, obj); m_item_set.insert(obj); // connectSignals(obj, false, true); ch = true; } // In theory, there should be more to be fetched if size < // totalItems. Sometimes those missing items still do not appear in // the fetched list, and we will have a perpetual "load more" // button. It turns out that the fetched replies lists has a // displayName, while the short one has not... this is a very ugly // hack indeed :) m_hasMore = !json.contains("displayName") && size() < m_totalItems; m_firstTime = false; if (ch) emit changed(); } //------------------------------------------------------------------------------ QString QASAbstractObjectList::urlOrProxy() const { return m_proxyUrl.isEmpty() ? m_url : m_proxyUrl; } //------------------------------------------------------------------------------ void QASAbstractObjectList::addObject(QASAbstractObject* obj) { if (m_item_set.contains(obj)) return; #ifdef DEBUG_QAS qDebug() << "addObject" << obj->apiLink(); #endif m_items.append(obj); m_item_set.insert(obj); // m_totalItems++; emit changed(); } //------------------------------------------------------------------------------ void QASAbstractObjectList::removeObject(QASAbstractObject* obj, bool signal) { #ifdef DEBUG_QAS qDebug() << "removeObject" << obj->apiLink(); #endif int idx = m_items.indexOf(obj); bool updatePrevLink = idx == 0; bool updateNextLink = idx == m_items.count()-1; m_items.removeAt(idx); m_item_set.remove(obj); if (m_items.count() > 0) { if (updateNextLink) m_nextLink = m_url + "?before=" + QUrl::toPercentEncoding(m_items.last()->apiLink()); if (updatePrevLink) m_prevLink = m_url + "?since=" + QUrl::toPercentEncoding(m_items.first()->apiLink()); } // m_totalItems--; if (signal) emit changed(); } pumpa-0.9.2/src/recipientedit.cpp0000644000175000017500000000560512653206625015476 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "recipientedit.h" #include #include //------------------------------------------------------------------------------ RecipientEdit::RecipientEdit(QWidget* parent) : QLineEdit(parent), m_completer(NULL) { } //------------------------------------------------------------------------------ void RecipientEdit::setChoices(QStringList choices) { m_choices = choices; if (m_completer) delete m_completer; m_completer = new QCompleter(this); m_completer->setWidget(this); m_completer->setCaseSensitivity(Qt::CaseInsensitive); QStringListModel* model = new QStringListModel(choices); m_completer->setModel(model); //m_completer->setCompletionMode(QCompleter::PopupCompletion); connect(m_completer, SIGNAL(activated(QString)), this, SLOT(insertCompletion(QString))); } //------------------------------------------------------------------------------ void RecipientEdit::keyPressEvent(QKeyEvent* event) { int key = event->key(); QLineEdit::keyPressEvent(event); QPair wordPos = wordPosAtCursor(); QString completionPrefix = text().mid(wordPos.first, wordPos.second); qDebug() << "completionPrefix" << completionPrefix; if (completionPrefix != m_completer->completionPrefix()) { m_completer->setCompletionPrefix(completionPrefix); m_completer->popup()->setCurrentIndex(m_completer->completionModel()->index(0, 0)); } if (!event->text().isEmpty() && completionPrefix.length() > 2) { m_completer->complete(); } } //------------------------------------------------------------------------------ QPair RecipientEdit::wordPosAtCursor() { static QRegExp whitespace("\\s+"); int cur = cursorPosition(); QString txt = text(); int startPos = cur ? txt.lastIndexOf(whitespace, cur-1) + 1 : 0; int endPos = txt.indexOf(whitespace, cur); if (endPos == -1) endPos = txt.count(); int len = endPos - startPos; return qMakePair(startPos, len); } //------------------------------------------------------------------------------ void RecipientEdit::insertCompletion(QString completion) { QPair wordPos = wordPosAtCursor(); setSelection(wordPos.first, wordPos.second); insert(completion); } pumpa-0.9.2/src/qasactorlist.h0000644000175000017500000000265412653206625015025 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "qasobjectlist.h" #include "qasactor.h" //------------------------------------------------------------------------------ class QASActorList : public QASObjectList { Q_OBJECT protected: QASActorList(QString url, QObject* parent); public: static void clearCache(); static QASActorList* getActorList(QVariantMap json, QObject* parent, int id=0); virtual QASActor* at(size_t i) const; void addActor(QASActor* actor) { addObject(actor); } void removeActor(QASActor* actor) { removeObject(actor); } bool onlyYou() const { return size()==1 && at(0)->isYou(); } // virtual void refresh(); QString actorNames() const; private: static QMap s_actorLists; }; pumpa-0.9.2/src/qasactor.cpp0000644000175000017500000001122412653206625014455 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "qasactor.h" #include "util.h" #include #include //------------------------------------------------------------------------------ QMap QASActor::s_actors; QSet QASActor::s_hiddenAuthors; bool QASActor::s_followed_known = false; void QASActor::clearCache() { deleteMap(s_actors); } //------------------------------------------------------------------------------ QASActor::QASActor(QString id, QObject* parent) : QASObject(id, parent), m_followed(false), m_followed_json(false), m_followed_set(false), m_isYou(false) { #ifdef DEBUG_QAS qDebug() << "new Actor" << m_id; #endif } //------------------------------------------------------------------------------ void QASActor::update(QVariantMap json) { #ifdef DEBUG_QAS qDebug() << "updating Actor" << m_id; #endif bool ch = false; bool dummy = false; m_json = json; m_author = NULL; updateVar(json, m_url, "url", ch); updateVar(json, m_displayName, "displayName", ch); updateVar(json, m_objectType, "objectType", ch); updateVar(json, m_preferredUsername, "preferredUsername", ch); updateVar(json, m_published, "published", ch); updateVar(json, m_updated, "updated", ch); m_webFinger = m_id; if (m_webFinger.startsWith("http://") || m_webFinger.startsWith("https://")) m_webFinger = m_preferredUsername; if (m_webFinger.startsWith("acct:")) m_webFinger.remove(0, 5); // this seems to be unreliable updateVar(json, m_followed_json, "pump_io", "followed", dummy); updateVar(json, m_summary, "summary", ch); updateVar(json, m_location, "location", "displayName", ch); QString oldUrl = m_imageUrl; if (json.contains("image")) { QVariantMap im = json["image"].toMap(); if (json.contains("status_net")) updateVar(im, m_imageUrl, "url", ch); else updateUrlOrProxy(im, m_imageUrl, ch); } if (ch) emit changed(); } //------------------------------------------------------------------------------ QASActor* QASActor::getActor(QVariantMap json, QObject* parent) { QString id = json["id"].toString(); Q_ASSERT_X(!id.isEmpty(), "getActor", serializeJsonC(json)); QASActor* act = s_actors.contains(id) ? s_actors[id] : new QASActor(id, parent); s_actors.insert(id, act); act->update(json); return act; } //------------------------------------------------------------------------------ bool QASActor::followed() const { return m_followed; } //------------------------------------------------------------------------------ void QASActor::setFollowed(bool b) { if (b != m_followed) { m_followed_set = true; m_followed = b; emit changed(); } } //------------------------------------------------------------------------------ QString QASActor::displayNameOrWebFinger() const { if (displayName().isEmpty()) return webFinger(); return displayName(); } //------------------------------------------------------------------------------ bool QASActor::isHidden() const { return s_hiddenAuthors.contains(m_id); } //------------------------------------------------------------------------------ void QASActor::setHidden(bool b) { if (b) s_hiddenAuthors.insert(m_id); else s_hiddenAuthors.remove(m_id); emit changed(); } //------------------------------------------------------------------------------ void QASActor::setHiddenAuthors(QStringList sl) { s_hiddenAuthors = sl.toSet(); } //------------------------------------------------------------------------------ void QASActor::setYou() { m_isYou = true; emit changed(); } //------------------------------------------------------------------------------ void QASActor::followedIsKnown() { emit changed(); } //------------------------------------------------------------------------------ void QASActor::setFollowedKnown() { s_followed_known = true; QMap::const_iterator it = s_actors.constBegin(); while (it != s_actors.constEnd()) { it.value()->followedIsKnown(); ++it; } } pumpa-0.9.2/src/json.h0000644000175000017500000000243412653206625013261 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _JSON_H_ #define _JSON_H_ #include //------------------------------------------------------------------------------ QVariantMap parseJson(QByteArray data); QByteArray serializeJson(QVariantMap json); const char* serializeJsonC(QVariantMap json); QString debugDumpJson(QVariantMap json, QString name = "", QString indent = ""); QString debugDumpJson(QVariantList json, QString name = "", QString indent = ""); QString debugDumpJson(QVariant json, QString name = "", QString indent = ""); #endif /* _JSON_H_ */ pumpa-0.9.2/src/objectlistwidget.h0000644000175000017500000000235612653206625015661 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _OBJECTLISTWIDGET_H_ #define _OBJECTLISTWIDGET_H_ #include "qactivitystreams.h" #include "aswidget.h" //------------------------------------------------------------------------------ class ObjectListWidget : public ASWidget { Q_OBJECT public: ObjectListWidget(QWidget* parent); QASObjectList* objectList() const; protected: virtual QASAbstractObjectList* initList(QString endpoint, QObject* parent); virtual void update(); virtual ObjectWidgetWithSignals* createWidget(QASAbstractObject* aObj); }; #endif /* _OBJECTLISTWIDGET_H_ */ pumpa-0.9.2/src/qasabstractobject.cpp0000644000175000017500000001402212653206625016336 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ //------------------------------------------------------------------------------ #include "qasabstractobject.h" //------------------------------------------------------------------------------ QDateTime parseTime(QString timeStr) { // 2013-05-28T16:43:06Z // 55 minutes ago kl. 20:39 -> 19:44 QDateTime dt = QDateTime::fromString(timeStr, Qt::ISODate); // "yyyy-MM-ddThh:mm:ssZ"); dt.setTimeSpec(Qt::UTC); return dt; } //------------------------------------------------------------------------------ QASAbstractObject::QASAbstractObject(int asType, QObject* parent) : QObject(parent), m_asType(asType) {} //------------------------------------------------------------------------------ void QASAbstractObject::connectSignals(QASAbstractObject* obj, bool changed, bool) { if (!obj) return; if (changed) connect(obj, SIGNAL(changed()), this, SIGNAL(changed()), Qt::UniqueConnection); // if (req) // connect(obj, SIGNAL(request(QString, int)), // parent(), SLOT(request(QString, int)), Qt::UniqueConnection); } //------------------------------------------------------------------------------ // void QASAbstractObject::refresh() { // QDateTime now = QDateTime::currentDateTime(); // if (m_lastRefreshed.isNull() || m_lastRefreshed.secsTo(now) > 1) // emit request(apiLink(), m_asType); // m_lastRefreshed = now; // } //------------------------------------------------------------------------------ void QASAbstractObject::updateVar(QVariantMap obj, QString& var, QString name, bool& changed) { QString oldVar = var; if (obj.contains(name)) var = obj[name].toString(); if (oldVar != var) changed = true; } //------------------------------------------------------------------------------ void QASAbstractObject::updateVar(QVariantMap obj, bool& var, QString name, bool& changed) { bool oldVar = var; if (obj.contains(name)) var = obj[name].toBool(); if (oldVar != var) changed = true; } //------------------------------------------------------------------------------ void QASAbstractObject::updateVar(QVariantMap obj, double& var, QString name, bool& changed) { double oldVar = var; if (obj.contains(name)) var = obj[name].toDouble(); if (oldVar != var) changed = true; } //------------------------------------------------------------------------------ void QASAbstractObject::updateVar(QVariantMap obj, qulonglong& var, QString name, bool& changed, bool ignoreDecrease) { qulonglong oldVar = var; if (obj.contains(name)) var = obj[name].toULongLong(); if ((var > oldVar) || ((var < oldVar) && !ignoreDecrease)) changed = true; } //------------------------------------------------------------------------------ void QASAbstractObject::updateVar(QVariantMap obj, QDateTime& var, QString name, bool& changed) { QDateTime oldVar = var; if (obj.contains(name)) var = parseTime(obj[name].toString()); if (oldVar != var) changed = true; } //------------------------------------------------------------------------------ void QASAbstractObject::updateVar(QVariantMap obj, QString& var, QString name1, QString name2, bool& changed) { if (obj.contains(name1)) updateVar(obj[name1].toMap(), var, name2, changed); } //------------------------------------------------------------------------------ void QASAbstractObject::updateVar(QVariantMap obj, bool& var, QString name1, QString name2, bool& changed) { if (obj.contains(name1)) updateVar(obj[name1].toMap(), var, name2, changed); } //------------------------------------------------------------------------------ void QASAbstractObject::updateVar(QVariantMap obj, double& var, QString name1, QString name2, bool& changed) { if (obj.contains(name1)) updateVar(obj[name1].toMap(), var, name2, changed); } //------------------------------------------------------------------------------ void QASAbstractObject::updateVar(QVariantMap obj, QString& var, QString name1, QString name2, QString name3, bool& changed) { if (obj.contains(name1)) updateVar(obj[name1].toMap(), var, name2, name3, changed); } //------------------------------------------------------------------------------ void QASAbstractObject::addVar(QVariantMap& obj, QString var, QString name) { if (var.isEmpty()) return; obj[name] = var; } //------------------------------------------------------------------------------ void QASAbstractObject::updateUrlOrProxy(QVariantMap obj, QString& var, bool& changed) { QString oldVar = var; bool dummy; updateVar(obj, var, "url", dummy); updateVar(obj, var, "pump_io", "proxyURL", dummy); if (oldVar.contains("/api/proxy/") && !var.contains("/api/proxy/")) var = oldVar; if (oldVar != var) changed = true; } //------------------------------------------------------------------------------ qint64 QASAbstractObject::sortIntByDateTime(QDateTime dt) { #if QT_VERSION >= 0x040700 return dt.toMSecsSinceEpoch(); #else return dt.toTime_t(); #endif } pumpa-0.9.2/src/collectionwidget.cpp0000644000175000017500000001234512653206625016204 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "collectionwidget.h" #include "pumpa_defines.h" #include "activitywidget.h" #include //------------------------------------------------------------------------------ CollectionWidget::CollectionWidget(QWidget* parent, int widgetLimit, int purgeWait) : ASWidget(parent, widgetLimit, purgeWait), m_loadOlderButton(NULL) {} //------------------------------------------------------------------------------ QASAbstractObjectList* CollectionWidget::initList(QString endpoint, QObject* parent) { m_asMode = QAS_COLLECTION; return QASCollection::initCollection(endpoint, parent); } //------------------------------------------------------------------------------ void CollectionWidget::clear() { ASWidget::clear(); m_loadOlderButton = new QPushButton(this); m_loadOlderButton->setFocusPolicy(Qt::NoFocus); connect(m_loadOlderButton, SIGNAL(clicked()), this, SLOT(onLoadOlderClicked())); m_loadOlderButton->setVisible(false); } //------------------------------------------------------------------------------ void CollectionWidget::onLoadOlderClicked() { updateLoadOlderButton(true); fetchOlder(); } //------------------------------------------------------------------------------ void CollectionWidget::updateLoadOlderButton(bool wait) { if (m_list->nextLink().isEmpty()) { m_loadOlderButton->setVisible(false); m_itemLayout->removeWidget(m_loadOlderButton); return; } QString text = tr("Load older"); if (wait) text = "..."; m_loadOlderButton->setText(text); m_loadOlderButton->setVisible(true); m_itemLayout->addWidget(m_loadOlderButton); } //------------------------------------------------------------------------------ void CollectionWidget::update() { ASWidget::update(); updateLoadOlderButton(); } //------------------------------------------------------------------------------ ObjectWidgetWithSignals* CollectionWidget::createWidget(QASAbstractObject* aObj) { QASActivity* act = qobject_cast(aObj); if (!act) { qDebug() << "ERROR CollectionWidget::createWidget passed non-activity"; return NULL; } ActivityWidget* aw = new ActivityWidget(act, isFullObject(act), this); connect(aw, SIGNAL(showContext(QASObject*)), this, SIGNAL(showContext(QASObject*))); // Add new object to shown objects set QASObject* obj = act->object(); if (obj) m_objects_shown.insert(obj); return aw; } //------------------------------------------------------------------------------ void CollectionWidget::changeWidgetObject(ObjectWidgetWithSignals* ow, QASAbstractObject* aObj) { QASActivity* act = qobject_cast(aObj); ActivityWidget* aw = qobject_cast(ow); if (!act || !aw) { if (!act) qDebug() << "[ERROR] CollectionWidget::changeWidgetObject, bad object"; if (!aw) qDebug() << "[ERROR] CollectionWidget::changeWidgetObject, bad widget"; return ASWidget::changeWidgetObject(ow, aObj); } aw->changeObject(act, isFullObject(act)); // Remove old object from shown objects set QASActivity* oldAct = aw->activity(); QASObject* oldObj = oldAct->object(); if (oldObj) m_objects_shown.remove(oldObj); // Add new object to shown objects set QASObject* obj = act->object(); if (obj) m_objects_shown.insert(obj); } //------------------------------------------------------------------------------ bool CollectionWidget::isFullObject(QASActivity* act) { QString verb = act->verb(); QASObject* obj = act->object(); bool objAlreadyShown = obj && m_objects_shown.contains(obj); QASActor* actor = act->actor(); bool directedAtYou = ((act->to() && act->to()->containsYou()) || (act->cc() && act->cc()->containsYou())) && !act->skipNotify(); bool hiddenActor = actor && actor->isHidden(); return (!(hiddenActor && !directedAtYou) && ((verb == "post" || verb == "share") && (!objAlreadyShown || directedAtYou))); } //------------------------------------------------------------------------------ bool CollectionWidget::hasObject(QASAbstractObject* aObj) { QASObject* obj = qobject_cast(aObj); return (m_object_set.contains(aObj) || (obj && m_objects_shown.contains(obj))); } //------------------------------------------------------------------------------ bool CollectionWidget::countAsNew(QASAbstractObject* aObj) { QASActivity* act = qobject_cast(aObj); if (!act) return false; return !act->actor()->isYou(); } pumpa-0.9.2/src/aswidget.cpp0000644000175000017500000001775312653206625014464 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "aswidget.h" #include "activitywidget.h" #include #include //------------------------------------------------------------------------------ ASWidget::ASWidget(QWidget* parent, int widgetLimit, int purgeWait) : QScrollArea(parent), m_firstTime(true), m_list(NULL), m_asMode(QAS_NULL), m_purgeWait(purgeWait), m_purgeCounter(purgeWait), m_widgetLimit(widgetLimit) { m_reuseWidgets = (m_widgetLimit > 0); m_itemLayout = new QVBoxLayout; m_itemLayout->setSpacing(10); m_listContainer = new QWidget; m_listContainer->setLayout(m_itemLayout); m_listContainer->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); setWidget(m_listContainer); setWidgetResizable(true); } //------------------------------------------------------------------------------ void ASWidget::clear() { QLayoutItem* item; while ((item = m_itemLayout->takeAt(0)) != 0) { if (dynamic_cast(item)) { QWidget* w = item->widget(); delete w; } delete item; } m_firstTime = true; m_itemLayout->addStretch(); } //------------------------------------------------------------------------------ void ASWidget::setEndpoint(QString endpoint, QObject* parent, int asMode) { clear(); m_list = initList(endpoint, parent); if (asMode != -1) m_asMode |= asMode; connect(m_list, SIGNAL(changed()), this, SLOT(update()), Qt::UniqueConnection); // connect(m_list, SIGNAL(request(QString, int)), // this, SIGNAL(request(QString, int)), Qt::UniqueConnection); } //------------------------------------------------------------------------------ void ASWidget::refresh() { int c = count(); if (c > 200) c = 200; // Count must be between 0 and 200 emit request(QString("%1?count=%2").arg(m_list->url()).arg(c), m_asMode | QAS_UPDATE_ONLY); } //------------------------------------------------------------------------------ void ASWidget::fetchNewer() { emit request(m_list->prevLink(), m_asMode | QAS_NEWER); } //------------------------------------------------------------------------------ void ASWidget::fetchOlder(int count) { m_purgeCounter = m_purgeWait; QString nextLink = m_list->nextLink(); if (!nextLink.isEmpty()) { if (count != -1) nextLink += QString("&count=%1").arg(count); emit request(nextLink, m_asMode | QAS_OLDER); } } //------------------------------------------------------------------------------ void ASWidget::refreshTimeLabels() { for (int i=0; icount(); i++) { ObjectWidgetWithSignals* ow = widgetAt(i); if (ow) ow->refreshTimeLabels(); } if (m_purgeCounter > 0) { m_purgeCounter--; #ifdef DEBUG_WIDGETS qDebug() << "purgeCounter" << m_purgeCounter << (m_list ? m_list->url() : "NULL"); #endif } } //------------------------------------------------------------------------------ void ASWidget::keyPressEvent(QKeyEvent* event) { int key = event->key(); if (key == Qt::Key_Home || key == Qt::Key_End) { bool home = key==Qt::Key_Home; QScrollBar* sb = verticalScrollBar(); sb->setValue(home ? sb->minimum() : sb->maximum()); } else { QScrollArea::keyPressEvent(event); } } //------------------------------------------------------------------------------ ObjectWidgetWithSignals* ASWidget::widgetAt(int idx) { QLayoutItem* item = m_itemLayout->itemAt(idx); if (dynamic_cast(item)) return qobject_cast(item->widget()); return NULL; } //------------------------------------------------------------------------------ QASAbstractObject* ASWidget::objectAt(int idx) { ObjectWidgetWithSignals* ows = widgetAt(idx); if (!ows) return NULL; ActivityWidget* aw = qobject_cast(ows); if (aw) return aw->activity(); ObjectWidget* ow = qobject_cast(ows); if (ow) return ow->object(); return NULL; } //------------------------------------------------------------------------------ void ASWidget::update() { /* We assume m_list contains all objects, but new ones might have been added either (or both) to the top or end. Go through from top (newest) to bottom. If the object doesn't exist add it, if it does increment the counter (go further down both in the collection and widget list). */ int li = 0; int newCount = 0; bool older = false; m_newObjects.clear(); for (size_t i=0; isize(); i++) { QASAbstractObject* cObj = m_list->at(i); if (cObj->isDeleted()) continue; #ifdef DEBUG_TIMELINE qDebug() << "UPDATE: m_list" << i << cObj->apiLink(); #endif QASAbstractObject* wObj = objectAt(li); if (wObj == cObj) { li++; older = true; #ifdef DEBUG_TIMELINE qDebug() << "UPDATE EXISTS1"; #endif continue; } if (m_object_set.contains(cObj)) { #ifdef DEBUG_TIMELINE qDebug() << "UPDATE EXISTS2"; #endif continue; } m_object_set.insert(cObj); bool doCountAsNew = false; bool doReuse = !older && m_reuseWidgets && (count() > m_widgetLimit) && m_purgeCounter == 0; if (doReuse) { ObjectWidgetWithSignals* ow = NULL; int idx = m_itemLayout->count(); while (!ow && --idx > 0) ow = widgetAt(idx); #ifdef DEBUG_WIDGETS qDebug() << "Reused widget" << idx << li << cObj->apiLink() << m_list->url(); #endif QASAbstractObject* obj = ow->asObject(); m_itemLayout->removeWidget(ow); m_object_set.remove(obj); m_list->removeObject(obj); #ifdef DEBUG_TIMELINE qDebug() << "UPDATE INSERTED AT" << li << "REUSE"; #endif changeWidgetObject(ow, cObj); m_itemLayout->insertWidget(li++, ow); doCountAsNew = countAsNew(cObj); } else { ObjectWidgetWithSignals* ow = createWidget(cObj); doCountAsNew = countAsNew(cObj); ObjectWidgetWithSignals::connectSignals(ow, this); #ifdef DEBUG_TIMELINE qDebug() << "UPDATE INSERTED AT" << li << "NEW"; #endif m_itemLayout->insertWidget(li++, ow); #ifdef DEBUG_WIDGETS qDebug() << "Created widget" << cObj->apiLink() << m_list->url(); #endif } if (!m_firstTime && doCountAsNew && !older) { newCount++; m_newObjects.push_back(cObj); } } if (newCount && !m_firstTime) emit hasNewObjects(); if (newCount && !isVisible() && !m_firstTime) emit highlightMe(); m_firstTime = false; } //------------------------------------------------------------------------------ void ASWidget::changeWidgetObject(ObjectWidgetWithSignals* ow, QASAbstractObject* obj) { ow->changeObject(obj); } //------------------------------------------------------------------------------ ObjectWidgetWithSignals* ASWidget::createWidget(QASAbstractObject*) { return NULL; } //------------------------------------------------------------------------------ QASAbstractObjectList* ASWidget::initList(QString, QObject*) { return NULL; } //------------------------------------------------------------------------------ void ASWidget::refreshObject(QASAbstractObject* obj) { if (!obj) return; QDateTime now = QDateTime::currentDateTime(); QDateTime lr = obj->lastRefreshed(); if (lr.isNull() || lr.secsTo(now) > 10) { obj->lastRefreshed(now); emit request(obj->apiLink(), obj->asType()); } } pumpa-0.9.2/src/QtKOAuth0000644000175000017500000000022412653206625013515 0ustar matsmats#include "kqoauthrequest.h" #include "kqoauthrequest_1.h" #include "kqoauthrequest_xauth.h" #include "kqoauthmanager.h" #include "kqoauthglobals.h" pumpa-0.9.2/src/json.cpp0000644000175000017500000000661112653206625013615 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "json.h" #ifdef QT5 #include #include #include #else #include #include #include #endif #include //------------------------------------------------------------------------------ QVariantMap parseJson(QByteArray data) { #ifdef QT5 return QJsonDocument::fromJson(data).object().toVariantMap(); #else QJson::Parser parser; bool ok; QVariantMap json = parser.parse(data, &ok).toMap(); if (!ok) qDebug() << "WARNING: Unable to parse JSON!" << data; return json; #endif } //------------------------------------------------------------------------------ QByteArray serializeJson(QVariantMap json) { #ifdef QT5 QJsonDocument jd(QJsonObject::fromVariantMap(json)); return jd.toJson(); #else QJson::Serializer serializer; QByteArray data = serializer.serialize(json); return data; #endif } //------------------------------------------------------------------------------ const char* serializeJsonC(QVariantMap json) { return QString(serializeJson(json)).toLatin1().data(); } //------------------------------------------------------------------------------ QString debugDumpJson(QVariantMap json, QString name, QString indent) { QString ret = "{"; QVariantMap::const_iterator it = json.constBegin(); for (; it != json.constEnd(); ++it) { ret += "\n" + indent + " " + it.key() + ": "; ret += debugDumpJson(it.value(), it.key(), indent); } ret += "\n" + indent + "}"; if (!name.isEmpty()) ret += " // end of " + name; return ret; } //------------------------------------------------------------------------------ QString debugDumpJson(QVariantList json, QString name, QString indent) { QString ret = "["; QVariantList::const_iterator it = json.constBegin(); for (; it != json.constEnd(); ++it) ret += debugDumpJson(*it, "", indent); ret += "\n" + indent + "]"; if (!name.isEmpty()) ret += " // end of " + name; return ret; } //------------------------------------------------------------------------------ QString debugDumpJson(QVariant json, QString name, QString indent) { int str_max_length = 70-indent.length(); QString ret; QVariant::Type type = json.type(); if (type == QVariant::Map) { ret += debugDumpJson(json.toMap(), name, indent + " "); } else if (json.canConvert()) { int ml = str_max_length - name.length(); json.convert(QVariant::String); QString s = json.toString(); if (s.length() > ml) s = s.left(ml-5) + " ... "; ret += s; } else if (type == QVariant::List) { ret += debugDumpJson(json.toList(), name, indent + " "); } else ret += "[" + QString(json.typeName()) + "]"; return ret; } pumpa-0.9.2/src/filedownloader.cpp0000644000175000017500000002414312653206625015642 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "filedownloader.h" #include "pumpa_defines.h" #include "pumpasettings.h" #include "util.h" #ifdef QT5 #include #else #include #endif #include //------------------------------------------------------------------------------ FileDownloadManager* FileDownloadManager::s_instance = NULL; //------------------------------------------------------------------------------ FileDownloadManager::FileDownloadManager(QObject* parent) : QObject(parent) { m_nextRequestId = 0; m_nam = new QNetworkAccessManager(this); // connect(m_nam, SIGNAL(sslErrors(QNetworkReply*, QList)), // this, SLOT(onSslErrors(QNetworkReply*, QList))); m_oam = new KQOAuthManager(this); connect(m_oam, SIGNAL(authorizedRequestReady(QByteArray, int)), this, SLOT(onAuthorizedRequestReady(QByteArray, int))); connect(m_oam, SIGNAL(sslErrors(QNetworkReply*, QList)), this, SLOT(onSslErrors(QNetworkReply*, QList))); } //------------------------------------------------------------------------------ FileDownloadManager* FileDownloadManager::getManager(QObject* parent) { if (s_instance == NULL && parent != 0) s_instance = new FileDownloadManager(parent); return s_instance; } //------------------------------------------------------------------------------ void FileDownloadManager::dumpStats() { // QMap m_inProgress; QMapIterator it(m_inProgress); while (it.hasNext()) { it.next(); qDebug() << "[FILEDOWNLOADMANAGER] in progress" << it.key(); } // QMap m_urlMap; // QMap m_requestMap; QMapIterator itt(m_requestMap); while (itt.hasNext()) { itt.next(); qDebug() << "[FILEDOWNLOADMANAGER] requests" << itt.key(); } } //------------------------------------------------------------------------------ bool FileDownloadManager::hasFile(QString url) { return !fileName(url).isEmpty(); } //------------------------------------------------------------------------------ QString FileDownloadManager::fileName(QString url) { QString fn = urlToPath(url); return QFile::exists(fn) ? fn : ""; } //------------------------------------------------------------------------------ QPixmap FileDownloadManager::pixmap(QString url, QString brokenImage) { QString fn = urlToPath(url); QPixmap pix(fn); // Sometimes files are given with the wrong file ending, or Qt is // unable to guess the format so we try to force them into popular // formats. if (pix.isNull()) pix.load(fn, "JPEG"); if (pix.isNull()) pix.load(fn, "PNG"); if (pix.isNull()) pix.load(fn, "GIF"); if (pix.isNull() && !brokenImage.isEmpty()) pix.load(brokenImage); return pix; } //------------------------------------------------------------------------------ QMovie* FileDownloadManager::movie(QString url) { QString fn = urlToPath(url); QMovie* mov = new QMovie(fn, QByteArray(), this); mov->setCacheMode(QMovie::CacheAll); return mov; } //------------------------------------------------------------------------------ bool FileDownloadManager::supportsAnimation(QString url) { QString fn = urlToPath(url); QImageReader r(fn); return r.supportsAnimation(); } //------------------------------------------------------------------------------ QString FileDownloadManager::urlToPath(QString url) { if (m_urlMap.contains(url)) return m_urlMap[url]; static QCryptographicHash hash(QCryptographicHash::Md5); static QStringList knownEndings; if (knownEndings.isEmpty()) knownEndings << ".png" << ".jpeg" << ".jpg" << ".gif"; if (m_cacheDir.isEmpty()) { m_cacheDir = #ifdef QT5 QStandardPaths::writableLocation(QStandardPaths::CacheLocation); #else QDesktopServices::storageLocation(QDesktopServices::CacheLocation); #endif if (m_cacheDir.isEmpty()) m_cacheDir = slashify(QDir::homePath())+".cache/"; else m_cacheDir = slashify(m_cacheDir); m_cacheDir += "pumpa/"; } QString path = m_cacheDir; QDir d; d.mkpath(path); QString ending; for (int i=0; i 32000) { // bound to be smaller than any MAX_INT m_nextRequestId = 0; while (m_requestMap.contains(m_nextRequestId)) m_nextRequestId++; } m_requestMap.insert(id, qMakePair(oar, fd)); m_oam->executeAuthorizedRequest(oar, id); } //------------------------------------------------------------------------------ void FileDownloadManager::onSslErrors(QNetworkReply* nr, QList) { (void) nr; // suppress unused warning #ifdef DEBUG_NET qDebug() << "FileDownloadManager SSL ERROR" << nr->url(); #endif } //------------------------------------------------------------------------------ void FileDownloadManager::onAuthorizedRequestReady(QByteArray response, int id) { QPair rp = m_requestMap.take(id); KQOAuthRequest* oar = rp.first; FileDownloader* fd = rp.second; fd->requestReady(response, oar); } //------------------------------------------------------------------------------ void FileDownloadManager::onFileReady(QString) { FileDownloader *fd = qobject_cast(sender()); if (!fd) return; m_inProgress.remove(fd->url()); fd->deleteLater(); } //------------------------------------------------------------------------------ FileDownloader::FileDownloader(QString url, FileDownloadManager* fdm) : QObject(fdm), m_url(url), m_oar(NULL), m_redirs(0), m_fdm(fdm) { PumpaSettings* ps = PumpaSettings::getSettings(); if (ps && m_url.startsWith(ps->siteUrl())) { m_oar = new KQOAuthRequest(this); m_oar->initRequest(KQOAuthRequest::AuthorizedRequest, QUrl(m_url)); m_oar->setConsumerKey(ps->clientId()); m_oar->setConsumerSecretKey(ps->clientSecret()); m_oar->setToken(ps->token()); m_oar->setTokenSecret(ps->tokenSecret()); m_oar->setHttpMethod(KQOAuthRequest::GET); m_oar->setTimeout(60000); // one minute time-out m_fdm->executeAuthorizedRequest(m_oar, this); } else { QNetworkReply* nr = m_fdm->m_nam->get(QNetworkRequest(QUrl(m_url))); connect(nr, SIGNAL(finished()), this, SLOT(replyFinished())); } } //------------------------------------------------------------------------------ void FileDownloader::replyFinished() { QNetworkReply *nr = qobject_cast(sender()); QString url = nr->url().toString(); int status = nr->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (nr->error()) { emit networkError(tr("Network error: ")+nr->errorString()); nr->deleteLater(); return; } if (status >= 301 && status <= 399) { if (m_redirs > 5) { emit networkError(tr("Network error: too many redirections!")); nr->deleteLater(); return; } QUrl newUrl = nr->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); m_redirs++; QNetworkReply* nr2 = m_fdm->m_nam->get(QNetworkRequest(nr->url().resolved(newUrl))); connect(nr2, SIGNAL(finished()), this, SLOT(replyFinished())); nr->deleteLater(); return; } requestReady(nr->readAll(), NULL); nr->deleteLater(); } //------------------------------------------------------------------------------ void FileDownloader::requestReady(QByteArray response, KQOAuthRequest* oar) { if (oar != NULL && m_fdm->m_oam->lastError()) { emit networkError(QString(tr("Unable to download %1 (Error #%2).")) .arg(m_url) .arg(m_fdm->m_oam->lastError())); return; } QString fn = m_fdm->urlToPath(m_url); QFile* fp = new QFile(fn); if (!fp->open(QIODevice::WriteOnly)) { emit networkError(QString(tr("Could not open file %1 for writing: ")). arg(fn) + fp->errorString()); delete fp; return; } fp->write(response); fp->close(); delete fp; QPixmap pix = m_fdm->pixmap(m_url); resizeImage(pix, fn); emit fileReady(); } //------------------------------------------------------------------------------ void FileDownloader::resizeImage(QPixmap pix, QString fn) { if (pix.isNull()) return; int w = pix.width(); int h = pix.height(); if (w <= IMAGE_MAX_WIDTH && h <= IMAGE_MAX_HEIGHT) return; QPixmap newPix; if (w > h) newPix = pix.scaledToWidth(IMAGE_MAX_WIDTH); else newPix = pix.scaledToHeight(IMAGE_MAX_HEIGHT); newPix.save(fn); } pumpa-0.9.2/src/aswidget.h0000644000175000017500000000544312653206625014122 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _ASWIDGET_H_ #define _ASWIDGET_H_ #include "qactivitystreams.h" #include "objectwidgetwithsignals.h" #include #include #include //------------------------------------------------------------------------------ class ASWidget : public QScrollArea { Q_OBJECT public: ASWidget(QWidget* parent, int widgetLimit=-1, int purgeWait=10); virtual void refreshTimeLabels(); virtual void fetchNewer(); virtual void fetchOlder(int count=-1); void refresh(); void setEndpoint(QString endpoint, QObject* parent, int asMode=-1); QString url() const { return m_list->url(); } int count() const { return m_object_set.size(); } const QList& newObjects() { return m_newObjects; } virtual bool hasObject(QASAbstractObject* obj) { return m_object_set.contains(obj); } bool linksInitialised() const { return !m_list->firstTime(); } signals: void highlightMe(); void hasNewObjects(); void request(QString, int); void newReply(QASObject*, QASObjectList*, QASObjectList*); void linkHovered(const QString&); void like(QASObject*); void share(QASObject*); void showContext(QASObject*); void follow(QString, bool); void deleteObject(QASObject*); void editObject(QASObject*); protected slots: virtual void update(); protected: virtual QASAbstractObjectList* initList(QString endpoint, QObject* parent); QASAbstractObject* objectAt(int idx); ObjectWidgetWithSignals* widgetAt(int idx); virtual ObjectWidgetWithSignals* createWidget(QASAbstractObject*); virtual void changeWidgetObject(ObjectWidgetWithSignals*, QASAbstractObject*); virtual bool countAsNew(QASAbstractObject*) { return true; } void keyPressEvent(QKeyEvent* event); virtual void clear(); void refreshObject(QASAbstractObject* obj); QVBoxLayout* m_itemLayout; QWidget* m_listContainer; bool m_firstTime; QSet m_object_set; QASAbstractObjectList* m_list; int m_asMode; bool m_reuseWidgets; int m_purgeWait; int m_purgeCounter; int m_widgetLimit; QList m_newObjects; }; #endif /* _ASWIDGET_H_ */ pumpa-0.9.2/src/util.h0000644000175000017500000000512412653206625013264 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _UTIL_H_ #define _UTIL_H_ #include #include #include //------------------------------------------------------------------------------ #define URL_REGEX "((https?://|(https?://)?www.)[^\\s\"]+\\.[^\\s\"<]+[^\\s\\.\\,\\!\\?\"<])" #define URL_REGEX_STRICT "(https?://[^\\s\"]+\\.[^\\s\"<]+[^\\s\\.\\,\\!\\?\\)\"<])" #define MD_NOGO_ITEMS "\\*`_" #define MD_PAIR_REGEX "%1([^\\s%3][^%3]*[^\\s%3]|[^\\s%3])%2" #define HTML_TAG_REGEX "<([^>]+)>" //------------------------------------------------------------------------------ /* Fixes site url, removes extra / from end, adds https:// if missing. */ QString siteUrlFixer(QString url, bool useSsl=true); /* Finds things that look like URLs and changes them into a href links. */ QString linkifyUrls(QString text); /* Finds things delimited by 'begin' and 'end' and changes them to be delimited by 'newBegin' and 'newEnd'. */ QString changePairedTags(QString text, QString begin, QString end, QString newBegin, QString newEnd, QString nogoItems = MD_NOGO_ITEMS); /* Transforms foo and https://bar.com to foo@bar.com */ QString siteUrlToAccountId(QString username, QString url); QString markDown(QString text); QString relativeFuzzyTime(QDateTime sTime, bool longTime=false); bool splitWebfingerId(QString accountId, QString& username, QString& server); template void deleteMap(QMap& map) { typename QMap::iterator i; for (i = map.begin(); i != map.end(); ++i) delete i.value(); map.clear(); } void checkMemory(QString desc=""); QString removeHtml(QString); QString addTextMarkup(QString content, bool useMarkdown); QString processTitle(QString text, bool removeLtgt); QString slashify(QString path); //------------------------------------------------------------------------------ #endif /* _UTIL_H_ */ pumpa-0.9.2/src/actorwidget.h0000644000175000017500000000314412653206625014623 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _ACTORWIDGET_H_ #define _ACTORWIDGET_H_ #include #include #include #include #include #include "qactivitystreams.h" //------------------------------------------------------------------------------ class ActorWidget : public QToolButton { Q_OBJECT public: ActorWidget(QASActor* a, QWidget* parent = 0, bool small=false); virtual ~ActorWidget(); void setActor(QASActor* a); signals: void follow(QString, bool); void lessClicked(); void moreClicked(); public slots: void onImageChanged(); void updatePixmap(); void updateMenu(); private slots: void onFollowAuthor(); void onHideAuthor(); void onMenuTitle(); private: void createMenu(); QASActor* m_actor; QString m_url; QString m_localFile; QMenu* m_menu; QAction* m_menuTitleAction; QAction* m_followAction; QAction* m_hideAuthorAction; }; #endif /* _ACTORWIDGET_H_ */ pumpa-0.9.2/src/qasactorlist.cpp0000644000175000017500000000440112653206625015350 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "qasactorlist.h" #include "util.h" #include //------------------------------------------------------------------------------ QMap QASActorList::s_actorLists; void QASActorList::clearCache() { deleteMap(s_actorLists); } //------------------------------------------------------------------------------ QASActorList::QASActorList(QString url, QObject* parent) : QASObjectList(url, parent) { m_asType = QAS_OBJECTLIST; #ifdef DEBUG_QAS qDebug() << "new ActorList" << m_url; #endif } //------------------------------------------------------------------------------ QASActorList* QASActorList::getActorList(QVariantMap json, QObject* parent, int id) { QString url = json["url"].toString(); if (url.isEmpty()) return NULL; QASActorList* ol = s_actorLists.contains(url) ? s_actorLists[url] : new QASActorList(url, parent); s_actorLists.insert(url, ol); ol->update(json, id & QAS_OLDER); return ol; } //------------------------------------------------------------------------------ QASActor* QASActorList::at(size_t i) const { if (i >= size()) return NULL; return QASObjectList::at(i)->asActor(); } //------------------------------------------------------------------------------ QString QASActorList::actorNames() const { QString text; for (size_t i=0; i%2") .arg(a->url()) .arg(a->displayNameOrYou()); if (i != size()-1) text += ", "; } return text; } pumpa-0.9.2/src/main.cpp0000644000175000017500000000777612653206625013605 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include #ifdef Q_OS_WIN32 #include #endif #include "pumpapp.h" #include "util.h" #include "pumpa_defines.h" #include "pumpasettings.h" #include #include //------------------------------------------------------------------------------ int testMarkup(QString str) { if (str.isEmpty()) // str = "Hello *world*, [Some Url](http://www.foo.bar/baz). Some\n" // "> block quoted text\n\n" // str = "markdowned [url](http://saz.im)\n\n" // "url on line http://saz.im and http://foo.bar\n" // "http://saz.im\n\n" // "with underlines: http://saz.im/foo_bar_baz.html ...\n" // "bold and broken html\n"; str = "![piktur](https://cloud.openmailbox.org/index.php/apps/files_sharing/ajax/publicpreview.php?x=1440&y=801&a=true&file=/bicinueva.JPG&t=4b267cd7510c0317681088a4b9a87588&scalingup=0)"; addTextMarkup(str, true); return 0; } //------------------------------------------------------------------------------ int main(int argc, char** argv) { QApplication app(argc, argv); app.setApplicationName(CLIENT_FANCY_NAME); app.setApplicationVersion(CLIENT_VERSION); QString locale = QLocale::system().name(); #ifdef Q_OS_WIN32 app.setStyle(QStyleFactory::create("Fusion")); #endif QString settingsFile; QStringList args = app.arguments(); if (args.count() > 1) { QString arg(args[1]); if (arg == "testmarkup") return testMarkup(argc > 2 ? args[2] : ""); else if (arg == "testfeedint") { qDebug() << PumpaSettingsDialog::feedIntToComboIndex(args[2].toInt()); return 0; } else if (arg == "autotestfeedint") { int (*f)(int) = PumpaSettingsDialog::feedIntToComboIndex; (void) f; Q_ASSERT(f(0) == 0); Q_ASSERT(f(1) == 0); Q_ASSERT(f(2) == 1); Q_ASSERT(f(3) == 0); Q_ASSERT(f(4) == 0); Q_ASSERT(f(5) == 0); Q_ASSERT(f(6) == 2); Q_ASSERT(f(11) == 0); Q_ASSERT(f(12) == 0); Q_ASSERT(f(14) == 3); Q_ASSERT(f(15) == 4); Q_ASSERT(f(16) == 0); Q_ASSERT(f(255) == 0); return 0; } else if (arg == "autotestcomboindex") { int (*f)(int) = PumpaSettingsDialog::comboIndexToFeedInt; (void) f; Q_ASSERT(f(0) == 0); Q_ASSERT(f(1) == 2); Q_ASSERT(f(2) == 6); Q_ASSERT(f(3) == 14); Q_ASSERT(f(4) == 15); Q_ASSERT(f(255) == 0); return 0; } else if (arg == "-l" && argc == 3) { locale = args[2]; } else if (arg == "-c" && argc == 3) { settingsFile = args[2]; } else { qDebug() << "Usage: ./pumpa [-c alternative.conf] [-l locale]"; return 0; } } PumpaSettings* settings = PumpaSettings::getSettings(true, settingsFile, &app); QString sLocale = settings->locale(); if (!sLocale.isEmpty()) locale = sLocale; qDebug() << "Using locale" << locale; QTranslator qtTranslator; qtTranslator.load("qt_" + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app.installTranslator(&qtTranslator); QTranslator translator; bool ok = translator.load(QString("pumpa_%1").arg(locale), ":/translations"); app.installTranslator(&translator); if (ok) qDebug() << "Successfully loaded translation"; PumpApp papp(settings, locale); return app.exec(); } pumpa-0.9.2/src/pumpasettings.h0000644000175000017500000001174612653206625015221 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _PUMPASETTINGS_H_ #define _PUMPASETTINGS_H_ #include #include #include #include #include #include #include "pumpa_defines.h" class PumpaSettings : public QObject { Q_OBJECT PumpaSettings(QString filename="", QObject* parent=0); public: static PumpaSettings* getSettings(bool create=false, QString filename="", QObject* parent=0); bool firstStart() const { return m_firstStart; } bool contains(QString key) const { return m_s->contains(key); } // getters QString siteUrl() const; QString userName() const { return getValue("username", "", "Account").toString(); } QString clientId() const { return getValue("oauth_client_id", "", "Account").toString(); } QString clientSecret() const { return getValue("oauth_client_secret", "", "Account").toString(); } QString token() const { return getValue("oauth_token", "", "Account").toString(); } QString tokenSecret() const { return getValue("oauth_token_secret", "", "Account").toString(); } int reloadTime() const; bool useTrayIcon() const { return getValue("use_tray_icon", false).toBool(); } int highlightFeeds() const; int popupFeeds() const; QSize size() const { return getValue("size", QSize(550, 500), "MainWindow").toSize(); } QPoint pos() const { return getValue("pos", QPoint(0, 0), "MainWindow").toPoint(); } int defaultToAddress() const { return getValue("default_to", RECIPIENT_PUBLIC).toInt(); } int defaultCcAddress() const { return getValue("default_cc", RECIPIENT_FOLLOWERS).toInt(); } bool commentOnComments() const { return getValue("comment_on_comments", false).toBool(); } bool useMarkdown() const { return getValue("use_markdown", false).toBool(); } bool ignoreSslErrors() const { return getValue("ignore_ssl_errors", false).toBool(); } QString locale() const { return getValue("locale", ""). toString(); } QString linkColor() const { return getValue("link_color", "").toString(); } QString firehoseUrl() const { return getValue("firehose_url", "https://ofirehose.com/feed.json").toString(); } int maxTimelineItems() const { return getValue("max_timeline_items", 20).toInt(); } int maxFirehoseItems() const { return getValue("max_timeline_items", 20).toInt(); } QStringList hideAuthors() const { return getValue("minimise_authors", QStringList()).toStringList(); } bool showPreview() const { return getValue("show_preview", true).toBool(); } bool showCharCount() const { return getValue("show_charcount", false).toBool(); } // setters void siteUrl(QString s) { setValue("site_url", s, "Account"); } void userName(QString s) { setValue("username", s, "Account"); } void clientId(QString s) { setValue("oauth_client_id", s, "Account"); } void clientSecret(QString s) { setValue("oauth_client_secret", s, "Account"); } void token(QString s) { setValue("oauth_token", s, "Account"); } void tokenSecret(QString s) { setValue("oauth_token_secret", s, "Account"); } void reloadTime(int i) { setValue("reload_time", i); } void useTrayIcon(bool b); void highlightFeeds(int i) { setValue("highlight_feeds", i); } void popupFeeds(int i) { setValue("popup_feeds", i); } void size(QSize s) { setValue("size", s, "MainWindow"); } void pos(QPoint p) { setValue("pos", p, "MainWindow"); } void defaultToAddress(int i) { setValue("default_to", i); } void defaultCcAddress(int i) { setValue("default_cc", i); } void commentOnComments(bool b) { setValue("comment_on_comments", b); } void useMarkdown(bool b) { setValue("use_markdown", b); } void ignoreSslErrors(bool b) { setValue("ignore_ssl_errors", b); } void hideAuthors(QStringList sl) { setValue("minimise_authors", sl); } void showPreview(bool b) { setValue("show_preview", b); } void showCharCount(bool b) { setValue("show_charcount", b); } signals: void trayIconChanged(); private: QVariant getValue(QString name, QVariant defaultValue=QVariant(), QString group="General") const; void setValue(QString name, QVariant value, QString group="General"); void readSettings(); bool m_firstStart; QSettings* m_s; static PumpaSettings* s_settings; }; #endif /* _PUMPASETTINGS_H_ */ pumpa-0.9.2/src/richtextlabel.cpp0000644000175000017500000000366012653206625015477 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "richtextlabel.h" //------------------------------------------------------------------------------ RichTextLabel::RichTextLabel(QWidget* parent, bool singleLine) : QLabel(parent), m_singleLine(singleLine) { // useful for debugging layouts and margins // setLineWidth(1); // setFrameStyle(QFrame::Box); setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); if (!m_singleLine) setWordWrap(true); setOpenExternalLinks(true); setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); setScaledContents(false); setTextFormat(Qt::RichText); setLineWidth(2); setMargin(0); //setFocusPolicy(Qt::NoFocus); setFocusPolicy(Qt::ClickFocus); setCursor(Qt::IBeamCursor); } //------------------------------------------------------------------------------ void RichTextLabel::resizeEvent(QResizeEvent*) { if (!m_singleLine && minimumSizeHint().width() > size().width()) { // qDebug() << "[DEBUG]: chop off" << minimumSizeHint().width() << size().width(); setStyleSheet("border-width: 2px; border-top-style: none; border-right-style: solid; border-bottom-style: none; border-left-style: none; border-color: red; "); } else { setStyleSheet(""); } } pumpa-0.9.2/src/tabwidget.h0000644000175000017500000000271412653206625014263 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef TABWIDGET_H #define TABWIDGET_H #include #include #include #include #include #include class TabWidget : public QTabWidget { Q_OBJECT public: TabWidget(QWidget* parent=0); int addTab(QWidget* page, const QString& label, bool highlight=true, bool closable=false); bool closable(int index) const { return m_okToClose.contains(index); } QWidget* closeCurrentTab(); public slots: void highlightTab(int index=-1); void deHighlightTab(int index=-1); protected slots: void closeTab(int index); protected: virtual void keyPressEvent(QKeyEvent* event); void addHighlightConnection(QWidget* page, int index); QSignalMapper* m_sMap; QSet m_okToClose; }; #endif pumpa-0.9.2/src/pumpapp.cpp0000644000175000017500000015444312653206625014335 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include #include #include #include #include #include #include "pumpapp.h" #include "json.h" #include "util.h" #include "filedownloader.h" #include "qaspell.h" #include "editprofiledialog.h" //------------------------------------------------------------------------------ PumpApp::PumpApp(PumpaSettings* settings, QString locale, QWidget* parent) : QMainWindow(parent), m_nextRequestId(0), m_s(settings), m_isLoading(false), m_wiz(NULL), m_messageWindow(NULL), m_editProfileDialog(NULL), m_trayIcon(NULL), m_locale(locale), m_uploadDialog(NULL), m_uploadRequest(NULL) { if (m_locale.isEmpty()) m_locale = "en_US"; #ifdef USE_ASPELL QASpell::setLocale(m_locale); #endif resize(m_s->size()); move(m_s->pos()); QASActor::setHiddenAuthors(m_s->hideAuthors()); // for old users set use_markdown=true, false for new installs if (!m_s->firstStart() && !m_s->contains("General/use_markdown")) { qDebug() << "Setting Markdown on by default for old users."; m_s->useMarkdown(true); } m_settingsDialog = new PumpaSettingsDialog(m_s, this); connect(m_settingsDialog, SIGNAL(newAccount()), this, SLOT(launchOAuthWizard())); QString linkColorStr = m_s->linkColor(); if (!linkColorStr.isEmpty()) { QColor linkColor(linkColorStr); if (linkColor.isValid()) { QPalette pal(qApp->palette()); pal.setColor(QPalette::Link, linkColor); pal.setColor(QPalette::LinkVisited, linkColor); qApp->setPalette(pal); } else { qDebug() << "[ERROR] cannot parse link_color \"" + linkColorStr + "\""; } } m_nam = new QNetworkAccessManager(this); connect(m_nam, SIGNAL(sslErrors(QNetworkReply*, QList)), this, SLOT(onSslErrors(QNetworkReply*, QList))); m_oam = new KQOAuthManager(this); connect(m_oam, SIGNAL(authorizedRequestReady(QByteArray, int)), this, SLOT(onAuthorizedRequestReady(QByteArray, int))); connect(m_oam, SIGNAL(sslErrors(QNetworkReply*, QList)), this, SLOT(onSslErrors(QNetworkReply*, QList))); m_fdm = FileDownloadManager::getManager(this); createActions(); createMenu(); #ifdef USE_DBUS m_dbus = new QDBusInterface("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications"); if (!m_dbus->isValid()) { qDebug() << "Unable to to connect to org.freedesktop.Notifications " "dbus service."; m_dbus = NULL; } #endif updateTrayIcon(); connect(m_s, SIGNAL(trayIconChanged()), this, SLOT(updateTrayIcon())); m_notifyMap = new QSignalMapper(this); int max_tl = m_s->maxTimelineItems(); int max_fh = m_s->maxFirehoseItems(); m_tabWidget = new TabWidget(this); m_tabWidget->setFocusPolicy(Qt::NoFocus); m_inboxWidget = new CollectionWidget(this, max_tl); connectCollection(m_inboxWidget); m_inboxMinorWidget = new CollectionWidget(this, max_tl); connectCollection(m_inboxMinorWidget); m_directMajorWidget = new CollectionWidget(this, max_tl); connectCollection(m_directMajorWidget); m_directMinorWidget = new CollectionWidget(this, max_tl); connectCollection(m_directMinorWidget); m_favouritesWidget = new ObjectListWidget(m_tabWidget); connectCollection(m_favouritesWidget); m_favouritesWidget->hide(); m_followersWidget = new ObjectListWidget(m_tabWidget); connectCollection(m_followersWidget); m_followersWidget->hide(); m_followingWidget = new ObjectListWidget(m_tabWidget); connectCollection(m_followingWidget, false); m_followingWidget->hide(); m_userActivitiesWidget = new CollectionWidget(this, max_tl); connectCollection(m_userActivitiesWidget, false); m_userActivitiesWidget->hide(); m_firehoseWidget = new CollectionWidget(this, max_fh, 0); connectCollection(m_firehoseWidget); m_firehoseWidget->hide(); connect(m_inboxMinorWidget, SIGNAL(hasNewObjects()), this, SLOT(onNewMinorObjects())); connect(m_directMinorWidget, SIGNAL(hasNewObjects()), this, SLOT(onNewMinorObjects())); connect(m_tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabSelected(int))); m_tabWidget->addTab(m_inboxWidget, tr("&Inbox")); m_tabWidget->addTab(m_directMinorWidget, tr("&Mentions")); m_tabWidget->addTab(m_directMajorWidget, tr("&Direct")); m_tabWidget->addTab(m_inboxMinorWidget, tr("Mean&while")); // m_tabWidget->addTab(m_firehoseWidget, tr("Fi&rehose"), true, true); m_notifyMap->setMapping(m_inboxWidget, FEED_INBOX); m_notifyMap->setMapping(m_directMinorWidget, FEED_MENTIONS); m_notifyMap->setMapping(m_directMajorWidget, FEED_DIRECT); m_notifyMap->setMapping(m_inboxMinorWidget, FEED_MEANWHILE); connect(m_notifyMap, SIGNAL(mapped(int)), this, SLOT(timelineHighlighted(int))); m_loadIcon = new QLabel(this); m_loadMovie = new QMovie(":/images/loader.gif", QByteArray(), this); statusBar()->addPermanentWidget(m_loadIcon); setWindowTitle(CLIENT_FANCY_NAME); setWindowIcon(QIcon(CLIENT_ICON)); setCentralWidget(m_tabWidget); // oaRequest->setEnableDebugOutput(true); // syncOAuthInfo(); m_timerId = -1; if (!haveOAuth()) launchOAuthWizard(); else startPumping(); } //------------------------------------------------------------------------------ PumpApp::~PumpApp() { m_s->size(size()); m_s->pos(pos()); m_s->hideAuthors(QASActor::getHiddenAuthors()); } //------------------------------------------------------------------------------ void PumpApp::launchOAuthWizard() { qApp->setQuitOnLastWindowClosed(false); if (!m_wiz) { m_wiz = new OAuthWizard(m_nam, m_oam, this); connect(m_wiz, SIGNAL(clientRegistered(QString, QString, QString, QString)), this, SLOT(onClientRegistered(QString, QString, QString, QString))); connect(m_wiz, SIGNAL(accessTokenReceived(QString, QString)), this, SLOT(onAccessTokenReceived(QString, QString))); connect(m_wiz, SIGNAL(accepted()), this, SLOT(show())); connect(m_wiz, SIGNAL(rejected()), this, SLOT(wizardCancelled())); } m_wiz->restart(); m_wiz->show(); } //------------------------------------------------------------------------------ QString certSubjectInfo(const QSslCertificate& cert) { #ifdef QT5 return cert.subjectInfo(QSslCertificate::CommonName).join(" "); #else return cert.subjectInfo(QSslCertificate::CommonName); #endif } QString certIssuerInfo(const QSslCertificate& cert) { #ifdef QT5 return cert.issuerInfo(QSslCertificate::CommonName).join(" "); #else return cert.issuerInfo(QSslCertificate::CommonName); #endif } //------------------------------------------------------------------------------ void PumpApp::onSslErrors(QNetworkReply* reply, QList errors) { if (m_s->ignoreSslErrors()) { reply->ignoreSslErrors(); return; } QString infoText; if (reply) infoText += "URL: " + reply->url().toString() + "\n"; for (int i=0; iUntrusted SSL connection!")); msgBox.setIcon(QMessageBox::Critical); msgBox.setInformativeText(infoText); if (!detailText.isEmpty()) msgBox.setDetailedText(detailText); msgBox.setStandardButtons(QMessageBox::Ignore | QMessageBox::Abort); msgBox.setDefaultButton(QMessageBox::Abort); if (msgBox.exec() == QMessageBox::Ignore) { reply->ignoreSslErrors(); return; } } //------------------------------------------------------------------------------ void PumpApp::startPumping() { resetActivityStreams(); QString webFinger = siteUrlToAccountId(m_s->userName(), m_s->siteUrl()); setWindowTitle(QString("%1 - %2").arg(CLIENT_FANCY_NAME).arg(webFinger)); // Setup endpoints for our timeline widgets m_inboxWidget->setEndpoint(inboxEndpoint("major"), this, QAS_FOLLOW); m_inboxMinorWidget->setEndpoint(inboxEndpoint("minor"), this); m_directMajorWidget->setEndpoint(inboxEndpoint("direct/major"), this); m_directMinorWidget->setEndpoint(inboxEndpoint("direct/minor"), this); m_followersWidget->setEndpoint(apiUrl(apiUser("followers")), this); m_followingWidget->setEndpoint(apiUrl(apiUser("following")), this, QAS_FOLLOW); m_favouritesWidget->setEndpoint(apiUrl(apiUser("favorites")), this); m_firehoseWidget->setEndpoint(m_s->firehoseUrl(), this); m_userActivitiesWidget->setEndpoint(apiUrl(apiUser("feed")), this); show(); m_recipientLists.clear(); addPublicRecipient(m_recipientLists); QVariantMap followersJson; followersJson["displayName"] = tr("Followers"); followersJson["objectType"] = "collection"; followersJson["id"] = apiUrl(apiUser("followers")); m_recipientLists.append(QASObject::getObject(followersJson, this)); request(apiUser("profile"), QAS_SELF_PROFILE); request(apiUser("lists/person"), QAS_SELF_LISTS); fetchAll(true); resetTimer(); } //------------------------------------------------------------------------------ void PumpApp::connectCollection(ASWidget* w, bool highlight) { connect(w, SIGNAL(request(QString, int)), this, SLOT(request(QString, int))); connect(w, SIGNAL(newReply(QASObject*, QASObjectList*, QASObjectList*)), this, SLOT(newNote(QASObject*, QASObjectList*, QASObjectList*))); connect(w, SIGNAL(linkHovered(const QString&)), this, SLOT(statusMessage(const QString&))); connect(w, SIGNAL(like(QASObject*)), this, SLOT(onLike(QASObject*))); connect(w, SIGNAL(share(QASObject*)), this, SLOT(onShare(QASObject*))); if (highlight) connect(w, SIGNAL(highlightMe()), m_notifyMap, SLOT(map())); connect(w, SIGNAL(showContext(QASObject*)), this, SLOT(onShowContext(QASObject*))); connect(w, SIGNAL(follow(QString, bool)), this, SLOT(follow(QString, bool))); connect(w, SIGNAL(deleteObject(QASObject*)), this, SLOT(onDeleteObject(QASObject*))); connect(w, SIGNAL(editObject(QASObject*)), this, SLOT(onEditObject(QASObject*))); } //------------------------------------------------------------------------------ void PumpApp::onClientRegistered(QString userName, QString siteUrl, QString clientId, QString clientSecret) { m_s->userName(userName); m_s->siteUrl(siteUrl); m_s->clientId(clientId); m_s->clientSecret(clientSecret); } //------------------------------------------------------------------------------ void PumpApp::onAccessTokenReceived(QString token, QString tokenSecret) { m_s->token(token); m_s->tokenSecret(tokenSecret); // syncOAuthInfo(); startPumping(); } //------------------------------------------------------------------------------ bool PumpApp::haveOAuth() { return !m_s->clientId().isEmpty() && !m_s->clientSecret().isEmpty() && !m_s->token().isEmpty() && !m_s->tokenSecret().isEmpty(); } //------------------------------------------------------------------------------ void PumpApp::tabSelected(int index) { m_tabWidget->deHighlightTab(index); resetNotifications(); m_closeTabAction->setEnabled(m_tabWidget->closable(index)); } //------------------------------------------------------------------------------ void PumpApp::timerEvent(QTimerEvent* event) { if (event->timerId() != m_timerId) return; m_timerCount++; if (m_timerCount >= m_s->reloadTime()) { m_timerCount = 0; fetchAll(false); } refreshTimeLabels(); } //------------------------------------------------------------------------------ void PumpApp::resetTimer() { if (m_timerId != -1) killTimer(m_timerId); m_timerId = startTimer(60*1000); // one minute timer m_timerCount = 0; } //------------------------------------------------------------------------------ void PumpApp::debugAction() { checkMemory("debug"); qDebug() << "inbox" << m_inboxWidget->count(); qDebug() << "meanwhile" << m_inboxMinorWidget->count(); qDebug() << "firehose" << m_firehoseWidget->count(); m_fdm->dumpStats(); } //------------------------------------------------------------------------------ void PumpApp::refreshTimeLabels() { m_inboxWidget->refreshTimeLabels(); m_directMinorWidget->refreshTimeLabels(); m_directMajorWidget->refreshTimeLabels(); m_inboxMinorWidget->refreshTimeLabels(); m_firehoseWidget->refreshTimeLabels(); for (int i=0; irefreshTimeLabels(); } //------------------------------------------------------------------------------ void PumpApp::statusMessage(const QString& msg) { statusBar()->showMessage(msg); } //------------------------------------------------------------------------------ void PumpApp::notifyMessage(QString msg) { statusMessage(msg); // qDebug() << "[STATUS]:" << msg; } //------------------------------------------------------------------------------ void PumpApp::timelineHighlighted(int feed) { bool doTrayIcon = (feed & m_s->highlightFeeds()) && m_trayIcon; bool doPopup = feed & m_s->popupFeeds(); // If we don't do any notifications don't even bother... if (!doTrayIcon && !doPopup) return; // We highlight the tray icon and generate popups only on certain // actions, so we first need to filter the list of new activities. QList acts; CollectionWidget* cw = qobject_cast(m_notifyMap->mapping(feed)); if (!cw) // if it wasn't a regular timeline we ignore it return; // We just keep posts, i.e. new notes or comments. // Other possibilities would be: follow favorite like QStringList keepVerbs; keepVerbs << "post"; // Filter: keep only activities that have a verb in keepVerbs const QList& ol = cw->newObjects(); for (int i=0; i(ol.at(0)); if (act && keepVerbs.contains(act->verb())) acts.push_back(act); } if (acts.isEmpty()) return; // Highlight tray icon. if (doTrayIcon) m_trayIcon->setIcon(QIcon(":/images/pumpa_glow.png")); // Popup notifications. if (doPopup) { int actsCount = 0; for (int i=0; iskipNotify()) actsCount++; if (actsCount == 0) return; QString msg = QString(tr("You have %Ln new notification(s).", 0, actsCount)); // If there's only a single post activity we'll make the // notification more informative. QASActivity* act = acts.at(0); QASObject* obj = act->object(); QASActor* actor = act->actor(); if (actsCount == 1 && act->verb() == "post" && obj && actor) { QString actorName = actor->displayNameOrWebFinger(); if (obj->type() == "comment") msg = QString(tr("%1 commented: ")).arg(actorName); else msg = QString(tr("%1 wrote: ")).arg(actorName); msg += "\"" + obj->excerpt() + "\""; } sendNotification(CLIENT_FANCY_NAME, msg); } } //------------------------------------------------------------------------------ void PumpApp::onNewMinorObjects() { CollectionWidget* cw = qobject_cast(sender()); if (!cw) return; const QList& newObjects = cw->newObjects(); cw->url(); for (int i=0; i(newObjects.at(i)); if (act && act->object() && act->object()->inReplyTo()) { QASObject* irtObj = act->object()->inReplyTo(); if (irtObj->url().isEmpty() || isShown(irtObj)) refreshObject(irtObj); } } } //------------------------------------------------------------------------------ void PumpApp::resetNotifications() { if (m_trayIcon) m_trayIcon->setIcon(QIcon(CLIENT_ICON)); m_tabWidget->deHighlightTab(); } //------------------------------------------------------------------------------ bool PumpApp::sendNotification(QString summary, QString text) { #ifdef USE_DBUS if (m_dbus && m_dbus->isValid()) { // https://developer.gnome.org/notification-spec/ QList args; args.append(CLIENT_NAME); // Application Name args.append(0123U); // Replaces ID (0U) args.append(QString()); // Notification Icon args.append(summary); // Summary args.append(text); // Body args.append(QStringList()); // Actions QVariantMap hints; // for hints to make icon, see // https://dev.visucore.com/bitcoin/doxygen/notificator_8cpp_source.html args.append(hints); args.append(3000); m_dbus->callWithArgumentList(QDBus::NoBlock, "Notify", args); return true; } #endif if (QSystemTrayIcon::supportsMessages() && m_trayIcon) { m_trayIcon->showMessage(CLIENT_FANCY_NAME, summary+" "+text); return true; } qDebug() << "[NOTIFY]" << summary << text; return false; } //------------------------------------------------------------------------------ void PumpApp::errorMessage(QString msg) { statusMessage(tr("Error: ") + msg); qDebug() << "[ERROR]:" << msg; } //------------------------------------------------------------------------------ void PumpApp::updateTrayIcon() { bool useTray = m_s->useTrayIcon() && QSystemTrayIcon::isSystemTrayAvailable(); if (useTray) { qApp->setQuitOnLastWindowClosed(false); if (!m_trayIcon) createTrayIcon(); else m_trayIcon->show(); if (m_trayIcon) { QString toolTip = CLIENT_FANCY_NAME; if (!m_s->userName().isEmpty()) toolTip += " - " + siteUrlToAccountId(m_s->userName(), m_s->siteUrl()); m_trayIcon->setToolTip(toolTip); } } else { qApp->setQuitOnLastWindowClosed(true); if (m_trayIcon) m_trayIcon->hide(); } } //------------------------------------------------------------------------------ void PumpApp::createTrayIcon() { m_trayIconMenu = new QMenu(this); m_trayIconMenu->addAction(newNoteAction); // m_trayIconMenu->addAction(newPictureAction); m_trayIconMenu->addSeparator(); m_trayIconMenu->addAction(m_showHideAction); m_trayIconMenu->addAction(exitAction); m_trayIcon = new QSystemTrayIcon(QIcon(CLIENT_ICON)); connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason))); m_trayIcon->setContextMenu(m_trayIconMenu); m_trayIcon->setToolTip(CLIENT_FANCY_NAME); m_trayIcon->show(); } //------------------------------------------------------------------------------ void PumpApp::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::Trigger) { m_trayIcon->setIcon(QIcon(CLIENT_ICON)); toggleVisible(); } } //------------------------------------------------------------------------------ QString PumpApp::showHideText(bool visible) { return QString(tr("%1 &Window")).arg(visible ? tr("Hide") : tr("Show") ); } //------------------------------------------------------------------------------ void PumpApp::toggleVisible() { setVisible(!isVisible()); m_showHideAction->setText(showHideText()); if (isVisible()) activateWindow(); } //------------------------------------------------------------------------------ void PumpApp::createActions() { exitAction = new QAction(tr("E&xit"), this); exitAction->setShortcut(tr("Ctrl+Q")); connect(exitAction, SIGNAL(triggered()), this, SLOT(exit())); openPrefsAction = new QAction(tr("Preferences"), this); connect(openPrefsAction, SIGNAL(triggered()), this, SLOT(preferences())); reloadAction = new QAction(tr("&Reload timeline"), this); reloadAction->setShortcut(tr("Ctrl+R")); connect(reloadAction, SIGNAL(triggered()), this, SLOT(reload())); loadOlderAction = new QAction(tr("Load older in timeline"), this); loadOlderAction->setShortcut(tr("Ctrl+O")); connect(loadOlderAction, SIGNAL(triggered()), this, SLOT(loadOlder())); followAction = new QAction(tr("F&ollow an account"), this); followAction->setShortcut(tr("Ctrl+L")); connect(followAction, SIGNAL(triggered()), this, SLOT(followDialog())); profileAction = new QAction(tr("Your &profile"), this); connect(profileAction, SIGNAL(triggered()), this, SLOT(editProfile())); aboutAction = new QAction(tr("&About"), this); connect(aboutAction, SIGNAL(triggered()), this, SLOT(about())); aboutQtAction = new QAction(tr("About &Qt"), this); connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt())); reportBugAction = new QAction(tr("Report &bug online"), this); connect(reportBugAction, SIGNAL(triggered()), this, SLOT(reportBug())); newNoteAction = new QAction(tr("New &Note"), this); newNoteAction->setShortcut(tr("Ctrl+N")); connect(newNoteAction, SIGNAL(triggered()), this, SLOT(newNote())); m_debugAction = new QAction("Debug", this); m_debugAction->setShortcut(tr("Ctrl+D")); connect(m_debugAction, SIGNAL(triggered()), this, SLOT(debugAction())); addAction(m_debugAction); m_closeTabAction = new QAction(tr("Close tab"), this); m_closeTabAction->setShortcut(tr("Ctrl+W")); connect(m_closeTabAction, SIGNAL(triggered()), this, SLOT(closeTab())); m_closeTabAction->setEnabled(false); m_firehoseAction = new QAction(tr("Firehose"), this); connect(m_firehoseAction, SIGNAL(triggered()), this, SLOT(showFirehose())); m_followersAction = new QAction(tr("Followers"), this); connect(m_followersAction, SIGNAL(triggered()), this, SLOT(showFollowers())); m_followingAction = new QAction(tr("Following"), this); connect(m_followingAction, SIGNAL(triggered()), this, SLOT(showFollowing())); m_favouritesAction = new QAction(tr("Favorites"), this); connect(m_favouritesAction, SIGNAL(triggered()), this, SLOT(showFavourites())); m_userActivitiesAction = new QAction(tr("Activities"), this); connect(m_userActivitiesAction, SIGNAL(triggered()), this, SLOT(showUserActivities())); m_showHideAction = new QAction(showHideText(true), this); connect(m_showHideAction, SIGNAL(triggered()), this, SLOT(toggleVisible())); } //------------------------------------------------------------------------------ void PumpApp::createMenu() { fileMenu = new QMenu(tr("&Pumpa"), this); fileMenu->addAction(newNoteAction); fileMenu->addSeparator(); fileMenu->addAction(followAction); fileMenu->addAction(profileAction); fileMenu->addAction(reloadAction); fileMenu->addAction(loadOlderAction); fileMenu->addSeparator(); fileMenu->addAction(openPrefsAction); fileMenu->addSeparator(); fileMenu->addAction(exitAction); menuBar()->addMenu(fileMenu); m_tabsMenu = new QMenu(tr("&Tabs"), this); m_tabsMenu->addAction(m_userActivitiesAction); m_tabsMenu->addAction(m_favouritesAction); m_tabsMenu->addAction(m_followersAction); m_tabsMenu->addAction(m_followingAction); m_tabsMenu->addAction(m_firehoseAction); m_tabsMenu->addAction(m_closeTabAction); menuBar()->addMenu(m_tabsMenu); helpMenu = new QMenu(tr("&Help"), this); helpMenu->addAction(aboutAction); helpMenu->addAction(aboutQtAction); helpMenu->addSeparator(); helpMenu->addAction(reportBugAction); menuBar()->addMenu(helpMenu); } //------------------------------------------------------------------------------ void PumpApp::preferences() { m_settingsDialog->exec(); } //------------------------------------------------------------------------------ void PumpApp::wizardCancelled() { qApp->setQuitOnLastWindowClosed(true); if (!haveOAuth()) exit(); } //------------------------------------------------------------------------------ void PumpApp::exit() { qApp->exit(); } //------------------------------------------------------------------------------ void PumpApp::about() { static const QString GPL = tr("

      Pumpa 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 3 of the License, or " "(at your option) any later version.

      " "

      Pumpa 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 Pumpa. If not, see " "http://www.gnu.org/licenses/." "

      "); static const QString credits = tr("

      The kQOAuth library" " is copyrighted by Johan " "Paul and licensed under LGPL 2.1.

      " "

      The sundown Markdown " "library is copyrighted by Natacha Porté, Vicent Marti and " "others, and " "permissively licensed.

      " "

      The Pumpa logo was " "" "created by Joshua Taylor for the " "Liberated Pixel Cup." "The logo is copyrighted by the artist and is dual licensed under the " "CC-BY-SA 3.0 license and the GNU GPL 3.0."); QString mainText = QString("

      %1 %2 - %3
      %4
      " + tr("Copyright © 2013-2015 Mats Sjöberg") + " - sazius@pump.saz.im." "

      " + tr("

      Report bugs and feature requests at " "%5.

      ")) .arg(CLIENT_FANCY_NAME) .arg(CLIENT_VERSION) .arg(tr("A simple Qt-based pump.io client.")) .arg(WEBSITE_URL) .arg(BUGTRACKER_URL); QMessageBox::about(this, QString(tr("About %1")).arg(CLIENT_FANCY_NAME), mainText + GPL + credits); } //------------------------------------------------------------------------------ void PumpApp::reportBug() { QDesktopServices::openUrl(QString(BUGTRACKER_URL)); } //------------------------------------------------------------------------------ void PumpApp::addPublicRecipient(RecipientList& rl) { QVariantMap publicJson; publicJson["displayName"] = tr("Public"); publicJson["objectType"] = "collection"; publicJson["id"] = PUBLIC_RECIPIENT_ID; rl.append(QASObject::getObject(publicJson, this)); } //------------------------------------------------------------------------------ void PumpApp::newNote(QASObject* obj, QASObjectList* to, QASObjectList* cc, bool edit) { if (!m_messageWindow) { m_messageWindow = new MessageWindow(m_s, &m_recipientLists, this); connect(m_messageWindow, SIGNAL(sendMessage(QString, QString, RecipientList, RecipientList)), this, SLOT(postNote(QString, QString, RecipientList, RecipientList))); connect(m_messageWindow, SIGNAL(sendImage(QString, QString, QString, RecipientList, RecipientList)), this, SLOT(postImage(QString, QString, QString, RecipientList, RecipientList))); connect(m_messageWindow, SIGNAL(sendReply(QASObject*, QString, RecipientList, RecipientList)), this, SLOT(postReply(QASObject*, QString, RecipientList, RecipientList))); connect(m_messageWindow, SIGNAL(sendEdit(QASObject*, QString, QString)), this, SLOT(postEdit(QASObject*, QString, QString))); m_messageWindow->setCompletions(&m_completions); } if (edit) m_messageWindow->editMessage(obj); else m_messageWindow->newMessage(obj, to, cc); m_messageWindow->show(); } //------------------------------------------------------------------------------ void PumpApp::reload() { fetchAll(true); refreshTimeLabels(); } //------------------------------------------------------------------------------ void PumpApp::fetchAll(bool all) { m_inboxWidget->fetchNewer(); if (m_inboxWidget->linksInitialised()) m_inboxWidget->refresh(); m_directMinorWidget->fetchNewer(); m_directMajorWidget->fetchNewer(); m_inboxMinorWidget->fetchNewer(); if (tabShown(m_firehoseWidget)) m_firehoseWidget->fetchNewer(); for (int i=0; ifetchNewer(); // These will be reloaded even if not shown, if all=true if (all || tabShown(m_followersWidget)) m_followersWidget->fetchNewer(); if (all || tabShown(m_followingWidget)) m_followingWidget->fetchNewer(); if (all || tabShown(m_favouritesWidget)) m_favouritesWidget->fetchNewer(); if (all || tabShown(m_userActivitiesWidget)) m_userActivitiesWidget->fetchNewer(); } //------------------------------------------------------------------------------ void PumpApp::loadOlder() { ASWidget* cw = qobject_cast(m_tabWidget->currentWidget()); if (cw) cw->fetchOlder(); } //------------------------------------------------------------------------------ bool PumpApp::isShown(QASAbstractObject* obj) { // check context widgets first for (int i=0; ihasObject(obj)) return true; // check all other tab widgets return m_inboxWidget->hasObject(obj) || m_directMinorWidget->hasObject(obj) || m_directMajorWidget->hasObject(obj) || m_inboxMinorWidget->hasObject(obj) || (tabShown(m_firehoseWidget) && m_firehoseWidget->hasObject(obj)) || (tabShown(m_followersWidget) && m_followersWidget->hasObject(obj)) || (tabShown(m_followingWidget) && m_followingWidget->hasObject(obj)) || (tabShown(m_favouritesWidget) && m_favouritesWidget->hasObject(obj)) || (tabShown(m_userActivitiesWidget) && m_userActivitiesWidget->hasObject(obj)); } //------------------------------------------------------------------------------ QString PumpApp::inboxEndpoint(QString path) { if (m_s->siteUrl().isEmpty()) { errorMessage(tr("Site not configured yet!")); return ""; } return m_s->siteUrl() + "/api/user/" + m_s->userName() + "/inbox/" + path; } //------------------------------------------------------------------------------ void PumpApp::onLike(QASObject* obj) { feed(obj->liked() ? "unlike" : "like", obj->toJson(), QAS_ACTIVITY | QAS_TOGGLE_LIKE); } //------------------------------------------------------------------------------ void PumpApp::onShare(QASObject* obj) { feed("share", obj->toJson(), QAS_ACTIVITY | QAS_REFRESH); } //------------------------------------------------------------------------------ void PumpApp::errorBox(QString msg) { QMessageBox::critical(this, CLIENT_FANCY_NAME, msg, QMessageBox::Ok); } //------------------------------------------------------------------------------ bool PumpApp::webFingerFromString(QString text, QString& username, QString& server) { if (text.startsWith("https://") || text.startsWith("http://")) { int slashPos = text.lastIndexOf('/'); if (slashPos > 0) text = siteUrlToAccountId(text.mid(slashPos+1), text.left(slashPos)); } return splitWebfingerId(text, username, server); } //------------------------------------------------------------------------------ void PumpApp::followDialog() { bool ok; QString defaultText = "evan@e14n.com"; QString cbText = QApplication::clipboard()->text(); if (cbText.contains('@') || cbText.startsWith("https://") || cbText.startsWith("http://")) defaultText = cbText; QString text = QInputDialog::getText(this, tr("Follow pump.io user"), tr("Enter webfinger ID of person to follow: "), QLineEdit::Normal, defaultText, &ok); if (!ok || text.isEmpty()) return; QString username, server; QString error; if (!webFingerFromString(text, username, server)) error = tr("Sorry, that doesn't even look like a webfinger ID!"); QASObject* obj = QASObject::getObject("acct:" + username + "@" + server); QASActor* actor = obj ? obj->asActor() : NULL; if (actor && actor->followed()) error = tr("Sorry, you are already following that person!"); if (!error.isEmpty()) return errorBox(error); testUserAndFollow(username, server); } //------------------------------------------------------------------------------ void PumpApp::editProfile() { request(apiUser("profile"), QAS_EDIT_PROFILE); } //------------------------------------------------------------------------------ void PumpApp::editProfileDialog() { if (!m_editProfileDialog) { m_editProfileDialog = new EditProfileDialog(this); connect(m_editProfileDialog, SIGNAL(profileEdited(QASActor*, QString)), this, SLOT(onProfileEdited(QASActor*, QString))); } m_editProfileDialog->setProfile(m_selfActor); m_editProfileDialog->show(); } //------------------------------------------------------------------------------ void PumpApp::onProfileEdited(QASActor* profile, QString newImageFile) { m_profile.clear(); m_profile["objectType"] = "person"; m_profile["displayName"] = profile->displayName(); m_profile["summary"] = profile->summary(); QVariantMap jsonLoc; jsonLoc["objectType"] = "place"; jsonLoc["displayName"] = profile->location(); m_profile["location"] = jsonLoc; if (newImageFile.isEmpty()) { uploadProfile(); } else { postAvatarImage(newImageFile); } } //------------------------------------------------------------------------------ void PumpApp::uploadProfile() { request(apiUser("profile"), QAS_SELF_PROFILE | QAS_REFRESH, KQOAuthRequest::PUT, m_profile); } //------------------------------------------------------------------------------ void PumpApp::testUserAndFollow(QString username, QString server) { QString fingerUrl = QString("%1/.well-known/webfinger?resource=%2@%1"). arg(server).arg(username); QNetworkRequest rec(QUrl("https://" + fingerUrl)); QNetworkReply* reply = m_nam->head(rec); connect(reply, SIGNAL(finished()), this, SLOT(userTestDoneAndFollow())); qDebug() << "testUserAndFollow" << fingerUrl; // isn't this an ugly yet fancy hack? :-) reply->setProperty("pumpa_redirects", 0); } //------------------------------------------------------------------------------ void PumpApp::userTestDoneAndFollow() { QString error; QNetworkReply *reply = qobject_cast(sender()); QUrl url = reply->url(); #ifdef QT5 QUrlQuery replyQuery(url.query()); QString userId = replyQuery.queryItemValue("resource"); #else QString userId = url.queryItemValue("resource"); #endif int redirs = reply->property("pumpa_redirects").toInt(); #ifdef DEBUG_NET qDebug() << "userTestDoneAndFollow" << url << redirs; #endif if (reply->error() != QNetworkReply::NoError) { if (redirs == 0) { url.setScheme("http"); QNetworkRequest rec(url); QNetworkReply* r = m_nam->head(rec); r->setProperty("pumpa_redirects", ++redirs); connect(r, SIGNAL(finished()), this, SLOT(userTestDoneAndFollow())); return; } else { return errorBox(tr("Invalid user: ") + userId); } } QUrl loc = reply->header(QNetworkRequest::LocationHeader).toUrl(); if (loc.isValid()) { if (redirs > 5) return errorBox(tr("Invalid user (cannot check site): ") + userId); reply->deleteLater(); QNetworkRequest rec(loc); QNetworkReply* r = m_nam->head(rec); r->setProperty("pumpa_redirects", ++redirs); connect(r, SIGNAL(finished()), this, SLOT(userTestDoneAndFollow())); return; } follow("acct:" + userId, true); } //------------------------------------------------------------------------------ bool PumpApp::tabShown(ASWidget* aw) const { return aw && m_tabWidget->indexOf(aw) != -1; } //------------------------------------------------------------------------------ void PumpApp::onShowContext(QASObject* obj) { ContextWidget* cw = new ContextWidget(this); connectCollection(cw); m_tabWidget->addTab(cw, tr("&Context"), true, true); cw->setObject(obj); m_tabWidget->setCurrentWidget(cw); m_contextWidgets.append(cw); } //------------------------------------------------------------------------------ void PumpApp::closeTab() { ContextWidget* cw = qobject_cast(m_tabWidget->closeCurrentTab()); if (cw) { int i = m_contextWidgets.indexOf(cw); if (i != -1) m_contextWidgets.removeAt(i); delete cw; } } //------------------------------------------------------------------------------ void PumpApp::showFirehose() { if (!tabShown(m_firehoseWidget)) m_tabWidget->addTab(m_firehoseWidget, tr("Fi&rehose"), true, true); m_tabWidget->setCurrentWidget(m_firehoseWidget); m_firehoseWidget->fetchNewer(); } //------------------------------------------------------------------------------ void PumpApp::showFollowers() { if (!tabShown(m_followersWidget)) m_tabWidget->addTab(m_followersWidget, tr("&Followers"), true, true); m_tabWidget->setCurrentWidget(m_followersWidget); m_followersWidget->fetchNewer(); } //------------------------------------------------------------------------------ void PumpApp::showFollowing() { if (!tabShown(m_followingWidget)) m_tabWidget->addTab(m_followingWidget, tr("F&ollowing"), false, true); m_tabWidget->setCurrentWidget(m_followingWidget); m_followingWidget->fetchNewer(); } //------------------------------------------------------------------------------ void PumpApp::showFavourites() { if (!tabShown(m_favouritesWidget)) m_tabWidget->addTab(m_favouritesWidget, tr("F&avorites"), false, true); m_tabWidget->setCurrentWidget(m_favouritesWidget); m_favouritesWidget->fetchNewer(); } //------------------------------------------------------------------------------ void PumpApp::showUserActivities() { if (!tabShown(m_userActivitiesWidget)) m_tabWidget->addTab(m_userActivitiesWidget, tr("A&ctivities"), false, true); m_tabWidget->setCurrentWidget(m_userActivitiesWidget); m_userActivitiesWidget->fetchNewer(); } //------------------------------------------------------------------------------ void PumpApp::postNote(QString content, QString title, RecipientList to, RecipientList cc) { if (content.isEmpty()) return; QVariantMap obj; obj["objectType"] = "note"; obj["content"] = addTextMarkup(content, m_s->useMarkdown()); QString ptitle = processTitle(title, false); if (!ptitle.isEmpty()) obj["displayName"] = ptitle; feed("post", obj, QAS_OBJECT | QAS_REFRESH | QAS_POST, to, cc); } //------------------------------------------------------------------------------ void PumpApp::postEdit(QASObject* obj, QString content, QString title) { QVariantMap json; json["id"] = obj->id(); json["objectType"] = obj->type(); json["content"] = addTextMarkup(content, m_s->useMarkdown()); QString ptitle = processTitle(title, false); if (!ptitle.isEmpty()) json["displayName"] = ptitle; feed("update", json, QAS_OBJECT | QAS_REFRESH | QAS_POST); } //------------------------------------------------------------------------------ void PumpApp::postImage(QString msg, QString title, QString imageFile, RecipientList to, RecipientList cc) { m_imageObject.clear(); m_imageObject["content"] = addTextMarkup(msg, m_s->useMarkdown()); m_imageObject["displayName"] = processTitle(title, false); m_imageTo = to; m_imageCc = cc; uploadFile(imageFile); } //------------------------------------------------------------------------------ void PumpApp::postAvatarImage(QString imageFile) { m_imageObject.clear(); m_imageTo = RecipientList(); m_imageCc = RecipientList(); addPublicRecipient(m_imageCc); uploadFile(imageFile, QAS_AVATAR_UPLOAD); } //------------------------------------------------------------------------------ void PumpApp::uploadFile(QString filename, int flags) { QString lcfn = filename.toLower(); QString mimeType; if (lcfn.endsWith(".jpg") || lcfn.endsWith(".jpeg")) mimeType = "image/jpeg"; else if (lcfn.endsWith(".png")) mimeType = "image/png"; else if (lcfn.endsWith(".gif")) mimeType = "image/gif"; else { qDebug() << "Cannot determine mime type of file" << filename; return; } QFile fp(filename); if (!fp.open(QIODevice::ReadOnly)) { qDebug() << "Unable to read file" << filename; return; } QByteArray ba = fp.readAll(); KQOAuthRequest* oaRequest = initRequest(apiUrl(apiUser("uploads")), KQOAuthRequest::POST); oaRequest->setContentType(mimeType); oaRequest->setContentLength(ba.size()); oaRequest->setRawData(ba); if (m_uploadDialog == NULL) { m_uploadDialog = new QProgressDialog("Uploading image...", "Abort", 0, 100, this); m_uploadDialog->setWindowModality(Qt::WindowModal); connect(m_uploadDialog, SIGNAL(canceled()), this, SLOT(uploadCanceled())); } else { m_uploadDialog->reset(); } m_uploadDialog->setValue(0); m_uploadDialog->show(); flags = QAS_IMAGE_UPLOAD | flags; m_uploadRequest = executeRequest(oaRequest, flags); connect(m_uploadRequest, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64))); } //------------------------------------------------------------------------------ void PumpApp::updatePostedImage(QVariantMap obj, int flags) { m_imageObject.unite(obj); // Work-around for https://github.com/e14n/pump.io/issues/885 // Thanks to Owen Shepherd for pointing this out! RecipientList to; to.append(m_selfActor); feed("update", m_imageObject, QAS_IMAGE_UPDATE | flags, to); } //------------------------------------------------------------------------------ void PumpApp::postImageActivity(QVariantMap, int flags) { if (flags == 0) flags = QAS_REFRESH; flags |= QAS_ACTIVITY | QAS_POST; feed("post", m_imageObject, flags, m_imageTo, m_imageCc); } //------------------------------------------------------------------------------ void PumpApp::uploadProgress(qint64 bytesSent, qint64 bytesTotal) { if (!m_uploadDialog || bytesTotal <= 0) return; m_uploadDialog->setValue((100*bytesSent)/bytesTotal); } //------------------------------------------------------------------------------ void PumpApp::uploadCanceled(bool abortRequest) { if (m_uploadRequest && abortRequest) { #ifdef DEBUG_NET qDebug() << "[DEBUG] aborting upload..."; #endif m_uploadRequest->abort(); } m_uploadRequest = NULL; m_imageObject.clear(); m_uploadDialog->reset(); } //------------------------------------------------------------------------------ void PumpApp::postReply(QASObject* replyToObj, QString content, RecipientList to, RecipientList cc) { if (content.isEmpty()) return; QVariantMap obj; obj["objectType"] = "comment"; obj["content"] = addTextMarkup(content, m_s->useMarkdown()); QVariantMap noteObj; noteObj["id"] = replyToObj->id(); noteObj["objectType"] = replyToObj->type(); obj["inReplyTo"] = noteObj; feed("post", obj, QAS_ACTIVITY | QAS_REFRESH | QAS_POST, to, cc); } //------------------------------------------------------------------------------ void PumpApp::follow(QString acctId, bool follow) { QVariantMap obj; obj["id"] = acctId; obj["objectType"] = "person"; int mode = QAS_ACTIVITY; if (follow) mode |= QAS_FOLLOW; else mode |= QAS_UNFOLLOW; feed(follow ? "follow" : "stop-following", obj, mode); } //------------------------------------------------------------------------------ void PumpApp::onDeleteObject(QASObject* obj) { QVariantMap json; json["id"] = obj->id(); json["objectType"] = obj->type(); feed("delete", json, QAS_ACTIVITY); } //------------------------------------------------------------------------------ void PumpApp::onEditObject(QASObject* obj) { newNote(obj, NULL, NULL, true); } //------------------------------------------------------------------------------ void PumpApp::addRecipient(QVariantMap& data, QString name, RecipientList to) { if (to.isEmpty()) return; QVariantList recList; for (int i=0; itype(); rec["id"] = obj->id(); if (!obj->proxyUrl().isEmpty()) { QVariantMap pump_io; pump_io["proxyURL"] = obj->proxyUrl(); rec["pump_io"] = pump_io; } recList.append(rec); } data[name] = recList; } //------------------------------------------------------------------------------ void PumpApp::feed(QString verb, QVariantMap object, int response_id, RecipientList to, RecipientList cc) { QString endpoint = "api/user/" + m_s->userName() + "/feed"; QVariantMap data; data["verb"] = verb; data["object"] = object; addRecipient(data, "to", to); addRecipient(data, "cc", cc); request(endpoint, response_id, KQOAuthRequest::POST, data); } //------------------------------------------------------------------------------ QString PumpApp::apiUrl(QString endpoint) { QString ret = endpoint; if (!ret.startsWith("http")) { if (ret[0] != '/') ret = '/' + ret; ret = m_s->siteUrl() + ret; } return ret; } //------------------------------------------------------------------------------ QString PumpApp::apiUser(QString path) { return QString("api/user/%1/%2").arg(m_s->userName()).arg(path); } //------------------------------------------------------------------------------ KQOAuthRequest* PumpApp::initRequest(QString endpoint, KQOAuthRequest::RequestHttpMethod method) { KQOAuthRequest* oaRequest = new KQOAuthRequest(this); oaRequest->initRequest(KQOAuthRequest::AuthorizedRequest, QUrl(endpoint)); oaRequest->setConsumerKey(m_s->clientId()); oaRequest->setConsumerSecretKey(m_s->clientSecret()); oaRequest->setToken(m_s->token()); oaRequest->setTokenSecret(m_s->tokenSecret()); oaRequest->setHttpMethod(method); oaRequest->setTimeout(60000); // one minute time-out return oaRequest; } //------------------------------------------------------------------------------ void PumpApp::request(QString endpoint, int response_id, KQOAuthRequest::RequestHttpMethod method, QVariantMap data) { endpoint = apiUrl(endpoint); bool firehose = (endpoint == m_s->firehoseUrl()); if (!endpoint.startsWith(m_s->siteUrl()) && !firehose) { #ifdef DEBUG_NET qDebug() << "[DEBUG] dropping request for" << endpoint; #endif return; } #ifdef DEBUG_NET qDebug() << (method == KQOAuthRequest::GET ? "[GET]" : method == KQOAuthRequest::POST ? "[POST]" : "[PUT]") << response_id << ":" << endpoint; #endif QStringList epl = endpoint.split("?"); KQOAuthRequest* oaRequest = initRequest(epl[0], method); // I have no idea why this is the only way that seems to // work. Incredibly frustrating and ugly :-/ if (epl.size() > 1) { KQOAuthParameters params; QStringList parts = epl[1].split("&"); for (int i=0; isetAdditionalParameters(params); } if (method == KQOAuthRequest::POST || method == KQOAuthRequest::PUT) { QByteArray ba = serializeJson(data); oaRequest->setRawData(ba); oaRequest->setContentType("application/json"); oaRequest->setContentLength(ba.size()); #ifdef DEBUG_NET qDebug() << "DATA" << oaRequest->rawData(); #endif } executeRequest(oaRequest, response_id); if (!m_isLoading) notifyMessage(tr("Loading ...")); setLoading(true); } //------------------------------------------------------------------------------ QNetworkReply* PumpApp::executeRequest(KQOAuthRequest* request, int response_id) { int id = m_nextRequestId++; if (m_nextRequestId > 32000) { // bound to be smaller than any MAX_INT m_nextRequestId = 0; while (m_requestMap.contains(m_nextRequestId)) m_nextRequestId++; } m_requestMap.insert(id, qMakePair(request, response_id)); m_oam->executeAuthorizedRequest(request, id); return m_oam->getReply(request); } //------------------------------------------------------------------------------ void PumpApp::followActor(QASActor* actor, bool doFollow) { actor->setFollowed(doFollow); QString from = QString("%1 (%2)").arg(actor->displayName()). arg(actor->webFinger()); if (from.isEmpty() || from.startsWith("http://") || from.startsWith("https://")) return; if (doFollow) m_completions.insert(from, actor); else m_completions.remove(from); } //------------------------------------------------------------------------------ void PumpApp::onAuthorizedRequestReady(QByteArray response, int rid) { KQOAuthManager::KQOAuthError lastError = m_oam->lastError(); QPair rp = m_requestMap.take(rid); KQOAuthRequest* request = rp.first; int id = rp.second; QString reqUrl = request->requestEndpoint().toString(); #ifdef DEBUG_NET_MOAR qDebug() << "[DEBUG] request done [" << rid << id << "]" << reqUrl << response.count() << "bytes"; #endif #ifdef DEBUG_NET_EVEN_MOAR qDebug() << "[DEBUG]" << response; #endif request->deleteLater(); if (m_requestMap.isEmpty()) { setLoading(false); notifyMessage(tr("Ready!")); } #ifdef DEBUG_NET_MOAR else { qDebug() << "[DEBUG] Still waiting for requests:"; QMapIterator i(m_requestMap); while (i.hasNext()) { i.next(); requestInfo_t ri = i.value(); qDebug() << " " << ri.first->requestEndpoint() << ri.second; } } #endif int sid = id & 0xFF; if (lastError) { if (id & QAS_POST) { errorMessage(tr("Unable to post message!")); m_messageWindow->show(); } else if (sid == QAS_IMAGE_UPLOAD) { uploadCanceled(false); errorMessage(tr("Unable to upload image!")); } else if (sid == QAS_OBJECT) { qDebug() << "[WARNING] unable to fetch context for object."; } else { errorMessage(QString(tr("Network or authorisation error [%1/%2] %3.")). arg(m_oam->lastError()).arg(id).arg(reqUrl)); } #ifdef DEBUG_NET qDebug() << "[ERROR]" << response; #endif return; } QVariantMap json = parseJson(response); if (sid == QAS_NULL) return; if (sid == QAS_COLLECTION) { QASCollection::getCollection(json, this, id); } else if (sid == QAS_ACTIVITY) { QASActivity* act = QASActivity::getActivity(json, this); if (act) { // if not a broken activity QASObject* obj = act->object(); if ((id & QAS_AVATAR_UPLOAD) && obj) { QVariantMap jsonImage; jsonImage["url"] = obj->imageUrl(); jsonImage["width"] = 96; jsonImage["height"] = 96; m_profile["image"] = jsonImage; uploadProfile(); } if ((id & QAS_TOGGLE_LIKE) && obj) obj->toggleLiked(); if ((id & QAS_FOLLOW) || (id & QAS_UNFOLLOW)) { QASActor* actor = obj ? obj->asActor() : NULL; if (actor) { bool doFollow = (id & QAS_FOLLOW); followActor(actor, doFollow); notifyMessage(QString(doFollow ? tr("Successfully followed ") : tr("Successfully unfollowed ")) + actor->displayNameOrWebFinger()); } } } } else if (sid == QAS_OBJECTLIST) { QASObjectList* ol = QASObjectList::getObjectList(json, this, id); if (ol && (id & QAS_FOLLOW)) { for (size_t i=0; isize(); ++i) { QASActor* actor = ol->at(i)->asActor(); if (actor) followActor(actor); } if (ol->nextLink().isEmpty()) QASActor::setFollowedKnown(); } } else if (sid == QAS_OBJECT) { QASObject::getObject(json, this); } else if (sid == QAS_ACTORLIST) { QASActorList::getActorList(json, this); } else if (sid == QAS_SELF_PROFILE || sid == QAS_EDIT_PROFILE) { m_selfActor = QASActor::getActor(json, this); m_selfActor->setYou(); if (sid == QAS_EDIT_PROFILE) editProfileDialog(); } else if (sid == QAS_SELF_LISTS) { QASObjectList* lists = QASObjectList::getObjectList(json, this, id); for (size_t i=0; isize(); ++i) { m_recipientLists.append(lists->at(i)); } } else if (sid == QAS_IMAGE_UPLOAD) { m_uploadDialog->reset(); updatePostedImage(json, id & QAS_AVATAR_UPLOAD); } else if (sid == QAS_IMAGE_UPDATE) { postImageActivity(json, id & QAS_AVATAR_UPLOAD); } if ((id & QAS_POST) && m_messageWindow && !m_messageWindow->isVisible()) m_messageWindow->clear(); if (id & QAS_REFRESH) { fetchAll(false); } } //------------------------------------------------------------------------------ // FIXME: this shouldn't be implemented in millions of places void PumpApp::refreshObject(QASAbstractObject* obj) { if (!obj) return; QDateTime now = QDateTime::currentDateTime(); QDateTime lr = obj->lastRefreshed(); if (lr.isNull() || lr.secsTo(now) > 10) { obj->lastRefreshed(now); request(obj->apiLink(), obj->asType()); } } //------------------------------------------------------------------------------ void PumpApp::setLoading(bool on) { if (!m_loadIcon || m_isLoading == on) return; m_isLoading = on; if (!on) { m_loadIcon->setMovie(NULL); m_loadIcon->setPixmap(QPixmap(":/images/empty.gif")); } else if (m_loadMovie->isValid()) { // m_loadIcon->setPixmap(QPixmap()); m_loadIcon->setMovie(m_loadMovie); m_loadMovie->start(); } } pumpa-0.9.2/src/editprofiledialog.cpp0000644000175000017500000001065712653206625016337 0ustar matsmats/* Copyright 2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include #include #include "editprofiledialog.h" #include "qasactor.h" #include "filedownloader.h" //------------------------------------------------------------------------------ EditProfileDialog::EditProfileDialog(QWidget* parent): QDialog(parent), m_profile(NULL) { m_imageFileName = ""; m_layout = new QVBoxLayout; m_imageLayout = new QHBoxLayout; m_imageLabel = new QLabel(); m_imageLayout->addWidget(m_imageLabel); m_changeImageButton = new TextToolButton(tr("&Change picture")); m_imageLayout->addWidget(m_changeImageButton); connect(m_changeImageButton, SIGNAL(clicked()), this, SLOT(onChangeImage())); m_layout->addLayout(m_imageLayout); m_realNameLabel = new QLabel(tr("Real name")); m_layout->addWidget(m_realNameLabel); m_realNameEdit = new QLineEdit; m_layout->addWidget(m_realNameEdit); m_hometownLabel = new QLabel(tr("Hometown")); m_layout->addWidget(m_hometownLabel); m_hometownEdit = new QLineEdit; m_layout->addWidget(m_hometownEdit); m_bioLabel = new QLabel(tr("Bio")); m_layout->addWidget(m_bioLabel); m_bioEdit = new QTextEdit; m_layout->addWidget(m_bioEdit); m_buttonBox = new QDialogButtonBox(this); m_buttonBox->setOrientation(Qt::Horizontal); m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(onOKClicked())); connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); m_layout->addWidget(m_buttonBox); setLayout(m_layout); } //------------------------------------------------------------------------------ void EditProfileDialog::setProfile(QASActor* profile) { m_realNameEdit->setText(profile->displayName()); m_hometownEdit->setText(profile->location()); m_bioEdit->setText(profile->summary()); m_profile = profile; m_imageFileName = ""; updateImage(); } //------------------------------------------------------------------------------ void EditProfileDialog::updateImage() { if (m_profile == NULL) return; QPixmap pix; if (!m_imageFileName.isEmpty()) { pix.load(m_imageFileName); } else { FileDownloadManager* fdm = FileDownloadManager::getManager(); QString imgSrc = m_profile->imageUrl(); if (fdm->hasFile(imgSrc)) { pix = fdm->pixmap(imgSrc, ":/images/image_broken.png"); } else if (!imgSrc.isEmpty()) { FileDownloader* fd = fdm->download(imgSrc); connect(fd, SIGNAL(fileReady()), this, SLOT(updateImage()), Qt::UniqueConnection); } } if (!pix.isNull()) { m_imageLabel->setPixmap(pix); m_imageLabel->setFixedSize(pix.size()); } } //------------------------------------------------------------------------------ void EditProfileDialog::onOKClicked() { QString newRealName = m_realNameEdit->text(); if (newRealName.isEmpty()) // to avoid having empty real name newRealName = m_profile->preferredUsername(); m_profile->setDisplayName(newRealName); m_profile->setLocation(m_hometownEdit->text()); m_profile->setSummary(m_bioEdit->toPlainText()); accept(); emit profileEdited(m_profile, m_imageFileName); } //------------------------------------------------------------------------------ void EditProfileDialog::onChangeImage() { QString fileName = QFileDialog::getOpenFileName(this, tr("Select Image"), "", tr("Image files (*.png *.jpg *.jpeg *.gif)" ";;All files (*.*)")); if (!fileName.isEmpty()) { QPixmap p; p.load(fileName); if (p.isNull()) { QMessageBox::critical(this, tr("Sorry!"), tr("That file didn't appear to be an image.")); } else { m_imageFileName = fileName; updateImage(); } } } pumpa-0.9.2/src/objectwidget.cpp0000644000175000017500000001365612653206625015325 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "objectwidget.h" //------------------------------------------------------------------------------ ObjectWidget::ObjectWidget(QASObject* obj, QWidget* parent) : ObjectWidgetWithSignals(parent), m_objectWidget(NULL), m_shortObjectWidget(NULL), m_contextLabel(NULL), m_contextButton(NULL), m_topLayout(NULL), m_object(NULL), m_irtObject(NULL), m_short(false) { #ifdef DEBUG_WIDGETS qDebug() << "Creating ObjectWidget"; #endif m_layout = new QVBoxLayout; m_layout->setContentsMargins(0, 0, 0, 0); m_layout->setSpacing(0); // Add label with context "Re:" text and "show context" button for // replies. m_topLayout = new QHBoxLayout; m_topLayout->setContentsMargins(0, 0, 0, 0); m_contextLabel = new RichTextLabel(this, true); m_topLayout->addWidget(m_contextLabel, 0, Qt::AlignVCenter); m_topLayout->addSpacing(10); m_contextButton = new TextToolButton(this); connect(m_contextButton, SIGNAL(clicked()), this, SLOT(onShowContext())); m_topLayout->addWidget(m_contextButton, 0, Qt::AlignVCenter); m_layout->addLayout(m_topLayout); m_objectWidget = new FullObjectWidget(m_object, this); ObjectWidgetWithSignals::connectSignals(m_objectWidget, this); connect(m_objectWidget, SIGNAL(lessClicked()), this, SLOT(showLess())); m_layout->addWidget(m_objectWidget); m_shortObjectWidget = new ShortObjectWidget(m_object, this); connect(m_shortObjectWidget, SIGNAL(follow(QString, bool)), this, SIGNAL(follow(QString, bool))); connect(m_shortObjectWidget, SIGNAL(moreClicked()), this, SLOT(showMore())); m_layout->addWidget(m_shortObjectWidget); changeObject(obj); setLayout(m_layout); } //------------------------------------------------------------------------------ ObjectWidget::~ObjectWidget() { #ifdef DEBUG_WIDGETS qDebug() << "Deleting ObjectWidget" << m_object->id(); #endif } //------------------------------------------------------------------------------ void ObjectWidget::changeObject(QASAbstractObject* obj, bool fullObject) { if (m_object) disconnect(m_object, SIGNAL(changed()), this, SLOT(onChanged())); if (m_irtObject) disconnect(m_irtObject, SIGNAL(changed()), this, SLOT(updateContextLabel())); m_irtObject = NULL; m_object = qobject_cast(obj); if (!obj) return; m_short = !fullObject; connect(m_object, SIGNAL(changed()), this, SLOT(onChanged())); m_objectWidget->changeObject(obj); m_shortObjectWidget->changeObject(obj); m_contextLabel->setVisible(false); m_contextButton->setVisible(false); if (m_object->type() == "comment" && m_object->inReplyTo()) { m_irtObject = m_object->inReplyTo(); connect(m_irtObject, SIGNAL(changed()), this, SLOT(updateContextLabel())); if (!m_irtObject->url().isEmpty()) updateContextLabel(); } if (m_short) { m_contextLabel->setVisible(false); m_contextButton->setVisible(false); m_objectWidget->setVisible(false); m_shortObjectWidget->setVisible(true); } else { m_shortObjectWidget->setVisible(false); m_objectWidget->setVisible(true); } QASActor* author = m_object->author(); if (author && author->url().isEmpty()) refreshObject(m_object); } //------------------------------------------------------------------------------ void ObjectWidget::showMore() { if (!m_short || !m_shortObjectWidget) return; m_short = false; m_shortObjectWidget->setVisible(false); m_objectWidget->setVisible(true); if (m_irtObject && !m_irtObject->url().isEmpty()) { m_contextLabel->setVisible(true); m_contextButton->setVisible(true); } m_objectWidget->updateMenu(); emit moreClicked(); } //------------------------------------------------------------------------------ void ObjectWidget::showLess() { if (m_short || !m_objectWidget) return; m_short = true; m_objectWidget->setVisible(false); m_shortObjectWidget->setVisible(true); m_contextLabel->setVisible(false); m_contextButton->setVisible(false); m_shortObjectWidget->updateMenu(); emit lessClicked(); } //------------------------------------------------------------------------------ void ObjectWidget::onChanged() { setVisible(!m_object->url().isEmpty()); } //------------------------------------------------------------------------------ void ObjectWidget::updateContextLabel() { if (!m_irtObject || !m_contextLabel) return; QString text = m_irtObject->excerpt(); m_contextLabel->setText(tr("Re: ") + text); m_contextButton->setText(tr("show context")); if (!m_short && !m_irtObject->url().isEmpty()) { m_contextLabel->setVisible(true); m_contextButton->setVisible(true); } } //------------------------------------------------------------------------------ void ObjectWidget::onShowContext() { if (!m_irtObject || !m_topLayout) return; emit showContext(m_irtObject); } //------------------------------------------------------------------------------ void ObjectWidget::refreshTimeLabels() { if (m_objectWidget) m_objectWidget->refreshTimeLabels(); if (m_shortObjectWidget) m_shortObjectWidget->refreshTimeLabels(); } //------------------------------------------------------------------------------ void ObjectWidget::disableLessButton() { if (!m_short) m_objectWidget->disableLessButton(); } pumpa-0.9.2/src/qasobject.cpp0000644000175000017500000002232112653206625014613 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #include "qasobject.h" #include "qasactor.h" #include "qasobjectlist.h" #include "qasactorlist.h" #include "util.h" #include #include #include #include //------------------------------------------------------------------------------ QMap QASObject::s_objects; void QASObject::clearCache() { deleteMap(s_objects); } int QASObject::objectsUnconnected() { int noConnections = 0; QMap hist; for (QMap::const_iterator it = s_objects.begin(); it != s_objects.end(); ++it) { int n = it.value()->connections(); hist[n] = hist.value(n, 0) + 1; if (n == 0) noConnections++; } qDebug() << "QASObject connections:"; QList keys = hist.keys(); qSort(keys); for (int i=0; iupdate(json["location"].toMap()); if (json.contains("inReplyTo")) { m_inReplyTo = QASObject::getObject(json["inReplyTo"].toMap(), parent(), true); //connectSignals(m_inReplyTo, true, true); } if (json.contains("author")) { m_author = QASActor::getActor(json["author"].toMap(), parent()); //connectSignals(m_author); } if (json.contains("replies")) { QVariantMap repliesMap = json["replies"].toMap(); // don't replace a list with an empty one... size_t rs = repliesMap["items"].toList().size(); if (!m_replies || rs>0) { m_replies = QASObjectList::getObjectList(repliesMap, parent()); m_replies->isReplies(true); // connectSignals(m_replies); } } if (json.contains("likes")) { m_likes = QASActorList::getActorList(json["likes"].toMap(), parent()); connectSignals(m_likes); } if (json.contains("shares")) { m_shares = QASActorList::getActorList(json["shares"].toMap(), parent()); connectSignals(m_shares); } if (isDeleted()) { m_content = ""; m_displayName = ""; if (!wasDeleted) ch = true; } if (ch) emit changed(); } //------------------------------------------------------------------------------ QASObject* QASObject::getObject(QVariantMap json, QObject* parent, bool ignoreLike) { QString id = json["id"].toString(); if (id.isEmpty()) // some objects from Mediagoblin seem to be without "id" id = json["url"].toString(); if (id.isEmpty()) { qDebug() << "WARNING: null object"; qDebug() << debugDumpJson(json, "object"); return NULL; } if (json["objectType"] == "person") return QASActor::getActor(json, parent); QASObject* obj = s_objects.contains(id) ? s_objects[id] : new QASObject(id, parent); s_objects.insert(id, obj); obj->update(json, ignoreLike); return obj; } //------------------------------------------------------------------------------ void QASObject::addReply(QASObject* obj) { if (!m_replies) { m_replies = QASObjectList::initObjectList(id() + "/replies", parent()); m_replies->isReplies(true); // connectSignals(m_replies); } m_replies->addObject(obj); #ifdef DEBUG_QAS qDebug() << "addReply" << obj->id() << "to" << id(); #endif emit changed(); } //------------------------------------------------------------------------------ void QASObject::toggleLiked() { m_liked = !m_liked; emit changed(); } //------------------------------------------------------------------------------ size_t QASObject::numLikes() const { return m_likes ? m_likes->size() : 0; } //------------------------------------------------------------------------------ void QASObject::addLike(QASActor* actor, bool like) { if (!m_likes) return; if (like) m_likes->addActor(actor); else m_likes->removeActor(actor); } //------------------------------------------------------------------------------ void QASObject::addShare(QASActor* actor) { if (!m_shares) return; m_shares->addActor(actor); } //------------------------------------------------------------------------------ size_t QASObject::numShares() const { return m_shares ? m_shares->size() : 0; } //------------------------------------------------------------------------------ size_t QASObject::numReplies() const { return m_replies ? m_replies->size() : 0; } //------------------------------------------------------------------------------ QString QASObject::apiLink() const { return !m_proxyUrl.isEmpty() ? m_proxyUrl : !m_apiLink.isEmpty() ? m_apiLink : m_id; } //------------------------------------------------------------------------------ QVariantMap QASObject::toJson() const { QVariantMap obj; obj["id"] = m_id; addVar(obj, m_content, "content"); addVar(obj, m_objectType, "objectType"); addVar(obj, m_url, "url"); addVar(obj, m_displayName, "displayName"); // addVar(obj, m_, ""); return obj; } //------------------------------------------------------------------------------ QASActor* QASObject::asActor() { return qobject_cast(this); } //------------------------------------------------------------------------------ QString QASObject::deletedText() const { return "[" + QString(tr("Deleted %1")).arg(deletedDate().toString()) + "]"; } //------------------------------------------------------------------------------ QString QASObject::content() const { return isDeleted() ? deletedText() : m_content; } //------------------------------------------------------------------------------ QString QASObject::excerpt() const { if (isDeleted()) return deletedText(); QString text = displayName(); if (text.isEmpty()) { text = content(); } if (!text.isEmpty()) { text.replace(QRegExp(HTML_TAG_REGEX), " "); } else { QString t = type(); text = (t == "image" ? "an " : "a ") + t; } return text.trimmed(); } pumpa-0.9.2/src/qascollection.h0000644000175000017500000000312412653206625015145 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _QASCOLLECTION_H_ #define _QASCOLLECTION_H_ #include "qasactivity.h" #include "qasabstractobject.h" #include "qasabstractobjectlist.h" //------------------------------------------------------------------------------ class QASCollection : public QASAbstractObjectList { Q_OBJECT protected: QASCollection(QString url, QObject* parent); public: static void clearCache(); static QASCollection* initCollection(QString url, QObject* parent); static QASCollection* getCollection(QVariantMap json, QObject* parent, int id); QASActivity* at(size_t i) const { return qobject_cast(QASAbstractObjectList::at(i)); } private: virtual QASAbstractObject* getAbstractObject(QVariantMap json, QObject* parent); static QMap s_collections; }; #endif /* _QASCOLLECTION_H_ */ pumpa-0.9.2/src/qasobjectlist.h0000644000175000017500000000366012653206625015161 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _QASOBJECTLIST_H_ #define _QASOBJECTLIST_H_ #include "qasabstractobject.h" #include "qasabstractobjectlist.h" #include "qasobject.h" //------------------------------------------------------------------------------ class QASObjectList : public QASAbstractObjectList { Q_OBJECT protected: QASObjectList(QString url, QObject* parent); public: virtual void update(QVariantMap json, bool older, bool updateOnly=false); static void clearCache(); static QASObjectList* initObjectList(QString url, QObject* parent); static QASObjectList* getObjectList(QVariantMap json, QObject* parent, int id=0); static QASObjectList* getObjectList(QVariantList json, QObject* parent, int id=0); QASObject* at(size_t i) const { return qobject_cast(QASAbstractObjectList::at(i)); } bool containsYou() const; void isReplies(bool b) { m_isReplies = b; } RecipientList toRecipientList() const; protected: virtual QASAbstractObject* getAbstractObject(QVariantMap json, QObject* parent); private: static QMap s_objectLists; bool m_isReplies; }; #endif /* _QASOBJECTLIST_H_ */ pumpa-0.9.2/src/qactivitystreams.h0000644000175000017500000000222312653206625015720 0ustar matsmats/* Copyright 2013-2015 Mats Sjöberg This file is part of the Pumpa programme. Pumpa 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 3 of the License, or (at your option) any later version. Pumpa 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 Pumpa. If not, see . */ #ifndef _QACTIVITYSTREAMS_H_ #define _QACTIVITYSTREAMS_H_ //------------------------------------------------------------------------------ #include "qasabstractobject.h" #include "qasobject.h" #include "qasactor.h" #include "qasactorlist.h" #include "qasactivity.h" #include "qasobjectlist.h" #include "qascollection.h" //------------------------------------------------------------------------------ void resetActivityStreams(); #endif /* _QACTIVITYSTREAMS_H_ */ pumpa-0.9.2/pumpa.qrc0000644000175000017500000000140512653206625013176 0ustar matsmats images/pumpa.png images/pumpa_glow.png images/default.png images/loader.gif images/empty.gif images/glyphicons_197_remove.png images/glyphicons_199_ban.png images/glyphicons_200_download.png translations/pumpa_en.qm translations/pumpa_es.qm translations/pumpa_fr.qm translations/pumpa_nvi.qm translations/pumpa_it.qm translations/pumpa_de.qm translations/pumpa_ru.qm translations/pumpa_fi.qm pumpa-0.9.2/icons/0000755000175000017500000000000012653206625012460 5ustar matsmatspumpa-0.9.2/icons/96x96/0000755000175000017500000000000012653206625013265 5ustar matsmatspumpa-0.9.2/icons/96x96/pumpa.png0000644000175000017500000000075512653206625015124 0ustar matsmatsPNG  IHDR``j !PLTEC2!%Q"5&D&BJTB{:IX tRNS@fbKGDH pHYs+XIDATX햱N0E#X*;'`v"1 ʄˆR^;# _}~"Q*JDzE1DDS{UM /T @@/~(u[[kk&лR7&:YZ[[U]_bw0Ɓ!cwXOj` N:(ޯ ~.ku< #0+tVYrex@m(>fI|fb-u0pߚN2D?Yx1rfK^JɄj;{F7ezRCcNO -gh%-p2 ̾PJ Nd_5 ͍IENDB`pumpa-0.9.2/icons/16x16/0000755000175000017500000000000012653206625013245 5ustar matsmatspumpa-0.9.2/icons/16x16/pumpa.png0000644000175000017500000000036112653206625015075 0ustar matsmatsPNG  IHDRRPLTEC2!%Q"5&D&BJTB{:IXntRNS@fbKGDH pHYs+_IDATc`X tYEzfѮ9 9hh/``H//o//2ҀTyX$= - ( d0%  !AA0BIENDB`pumpa-0.9.2/icons/24x24/0000755000175000017500000000000012653206625013243 5ustar matsmatspumpa-0.9.2/icons/24x24/pumpa.png0000644000175000017500000000045012653206625015072 0ustar matsmatsPNG  IHDRY !PLTEC2!%Q"5&D&BJTB{:IX tRNS@fbKGDH pHYs+IDATӝͽ 1 !0)0 PR+Z M"/`g˖$WDbۘY|b}wv?Y˨׵cAUz u}Ʃ pF93 m":u)-)'O-;?$IENDB`pumpa-0.9.2/icons/pumpa.xpm0000644000175000017500000000261512653206625014334 0ustar matsmats/* XPM */ static char *pumpa[] = { /* columns rows colors chars-per-pixel */ "32 32 11 1 ", " c #322125", ". c #43181C", "X c #51221E", "o c #803526", "O c #8F4426", "+ c #FF7B3A", "@ c #AE424A", "# c #FF5442", "$ c #FFA749", "% c #FFD558", "& c None", /* pixels */ "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&++&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&OoO&&&&&&&&&&&&&&&", "&&&&&&&&&&++++ooO+++++&&&&&&&&&&", "&&&&&&&&++###$$ooO$$##+++&&&&&&&", "&&&&&&&+$$$$$#+ooX+#$$$$$#&&&&&&", "&&&&&&+$%$$$$++XX.++$%%$%+#&&&&&", "&&&&&&+%+$$$%$+++++$%%$$$%+#&&&&", "&&&&&#%++$$%$$$$%%%$%$$$+$+#&&&&", "&&&&&#%+$$+%$$$$$$$$$++$++$+@&&&", "&&&&#+$+$++$++$$$$+++$++$+$+@&&&", "&&&&#++++++$+++$$++++$++$+++@&&&", "&&&&#++++++$++++$++++$++++++@&&&", "&&&&#+#+$+++++++++++++++$+#+@&&&", "&&&&#+#+$+++++++++++++++++#+@&&&", "&&&&#+#+++++#+++$+++#+++++#@&&&&", "&&&&#++#++++#+++$+++#++#+##@&&&&", "&&&&&#++#+++#++++++#+++####@&&&&", "&&&&&##+#+#++#+++++#++####@&&&&&", "&&&&&&@#####+#++#++#####@#@ &&&&", "&&&&&& @##@##@####@####@#@ &&&", "&&&&& @@#@##@###@###@@@ &&&", "&&&&& @@@@@@@@@@@@@ &&&", "&&&&&& &&&&", "&&&&&&&&& &&&&&&&", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" }; pumpa-0.9.2/icons/pumpa.ico0000644000175000017500000000121712653206625014277 0ustar matsmats  yPNG  IHDR szz@IDATXU1k@}J &v( lEB5ݥLN !xn/h YT.z'NwxH{ݻp_A[-ޖǘq EeξOS)[(o2UXd<3Kas6wx~mRLnP-P&R? 396'' nA .#GşRBGVd ЦKa*Tb Du3խ\^a45[0MjGP}_d L JCY)x_u$I.Y)52&ĄfQ$4m)ENB3JXeT~meu_nxl>^UcZ*@0xCZQ|s M6 s5"ŘaVפ @oSq9Lŵ1pbry,s#V1d7[U6& Y5=?MlڛHvmڛ0^Rzh,0}0yw[qo_I6-P1!}hLݠ1/m @L8'dcrO(kDky}@=4McZȒ!ѢUI$I$ILzpR IENDB`pumpa-0.9.2/icons/pumpa.icns0000644000175000017500000000567112653206625014471 0ustar matsmatsicns is32ن mՠDŽ 稩 0,cΤ21*9>?'7y|}[$065@ ,   ;a22{)zr{-qtx}}{{}ssrbhe[\YZXT=hzGWi6ETq}V-~,HLPRTOSI?!!#$%$554547, ""%A#.!!:O;<<,7<<::9::??=:557244@@ABTt&:;*.A9&]7B AB,-EJLJ3-?VPJ?GMIIALLJRNIBAAFDJI?>HEKM><==:;=9=<=>=:<;>:<:;BIH$?<;;=;=<<=?Ha]5FGGCA>=;:>>AG@ 3FHGFEHEIA%$#%&'"):;;::;3#$&&) ">!&4%%s8mkrxx|LF } }F ?%ܯN3Ŗ+)JCDDDBM+il32QQQC222222222{{D5D{55D{{{T55DTT{{T{55"{TT{Ձ {{""{{է{T{{է{Հ{TT{{ՁՀ{{TT{{Ն{{{{{B T{{{{{{{{{{{BT{{{{{{BT{{{{BT{T{{{T{BT{T{{T{BT{T{T{{T{TBT{{T{T{{T{{T{TTBT{{T{T{T{TBTT{T{T{{T{T{{TBBT{T{{T{{TBTB!!BTTBTTBTBTBTB!!BBTBTTBTBTB!!B!!!::&:&:::BII&IIBB::IB:&&:BIB:IXI ::::IXXIX:B:X:IXI:IXXIX:BBX::IIXIXIXI:I:BBX:II:XI::I::I:J B:I:I::I::I:I::I:I:JB:I:II:I::I:JB:I:I:I:JB:B:I:I:B:JB:B:I:B:JB:B:B:I:B:BJB::B:B:I:B::B:BBJB::B:B:B:BJBB:B:B::B:B::BJJB:B::B::BJBJ%%JBBJBBJBJBJBJ%%JJBJBBJBJBJ%%J%%%l8mkpumpa-0.9.2/icons/32x32/0000755000175000017500000000000012653206625013241 5ustar matsmatspumpa-0.9.2/icons/32x32/pumpa.png0000644000175000017500000000054312653206625015073 0ustar matsmatsPNG  IHDR Tg!PLTEC2!%Q"5&D&BJTB{:IX tRNS@fbKGDH pHYs+IDAT(ϵ;0Ј\sD)k:$vL7'uO++oج?p] XDv^> (4G /D1@. a]@{ $@`]鈾lZuTtAuVYkLcOm2TFN? Atf+IENDB`pumpa-0.9.2/icons/64x64/0000755000175000017500000000000012653206625013253 5ustar matsmatspumpa-0.9.2/icons/64x64/pumpa.png0000644000175000017500000000057312653206625015110 0ustar matsmatsPNG  IHDR@@XGl!PLTEC2!%Q"5&D&BJTB{:IX tRNS@fbKGDH pHYs+IDATH0  ]XBVx+dNiPw%gN}OSfnZ3a1#b]c8 B`Y^Ibkh}9ࢱRm5*l}ca$4@wnQ&#>뾊1zM48V_Q 6cȒfꘘrd[{?@DMԃFIT s' ActivityWidget via %1 через %1 To: Кому: CC: Копия: Public Публично ActorWidget stop following Отписаться follow Подписаться stop minimising posts Не сворачивать сообщения auto-minimise posts Автоматически сворачивать сообщения Are you sure you want to stop following %1? Вы хотите отписаться от %1? CollectionWidget Load older Загрузить предыдущие сообщения EditProfileDialog &Change picture &Изменить изображение Real name Настоящее Имя Hometown Родной город Bio Биография Select Image Выбрать Изображение Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Файлы изображений (*.png *.jpg *.jpeg *.gif);;Все файлы (*.*) Sorry! Простите! That file didn't appear to be an image. Этот файл не является изображением. FileDownloader Network error: Ошибка сети: Network error: too many redirections! Ошибка сети: слишком много перенаправлений! Unable to download %1 (Error #%2). Не могу скачать %1 (Ошибка #%2) Could not open file %1 for writing: Не могу открыть файл %1 для записи: FullObjectWidget delete удалить edit изменить comment Button to comment on a post комментировать [No description] [Нет описания] at %1 location of person @ %1 Profile last updated %1 Профиль был обновлён %1 updated %1 обновлено КОГДА обновлено %1 at %1 time when post was published @ %1 unlike Такой перевод не корректен, поскольку он конфликтует с командой "dislike", которую Pumpa в данный момент не поддерживает. Правильным переводом было бы "больше не нравится", либо "разонравилось", но первое слишком длинно для кнопки, а второе звучит слишком панибратски. не нравится like нравится share пересказать stop following отписаться follow подписаться follow %1 подписаться на %1 You like this. Вам это нравится. %1 likes this. %1 это нравится. %1 like this. %1 это нравится and %Ln other person(s) и %n другой человек и %n других человека и %n других человек shared this. Many persons shared пересказали это. shared this. One person shared пересказал это. %Ln person(s) shared this. %n человек пересказал это. %n человека пересказали это. %n человек пересказали это. Show all replies Показать все комментарии post Name of object type. сообщение note Name of object type. Здесь принято использовать слово "заметка", но с ним не будут получаться согласованные сообщения, так что я использовал то же слово, что и для post сообщение comment Name of object type. комментарий image Name of object type. изображение Are you sure you want to delete this %1? вы уверены, что хотите удалить это %1 Share this %1 by %2? Поделиться этим %1м от %2? MessageEdit Spelling suggestions... Варианты правописания... MessageRecipients - - MessageWindow Use Markdown Использовать Markdown [help] [помощь] To: Кому: Cc: Копия: &Remove picture &Удалить изображение + &To + &Кому + &Cc + Ко&пия Title (optional) Заголовок (не обязательно) Ca&ncel От&мена &Preview Пре&дпросмотр &Send message &Отправить сообщение Select recipient (To) Выбрать получателя (Кому) Select recipient (Cc) Выбрать получателя (Копия) Post a reply Или "опубликовать ответ" или "написать ответ" Отправить ответ Post a note Отправить сообщение &Send comment &Отправить комментарий &Send post &Отправить сообщение Edit object Изменить объект Edit post Изменить сообщение Edit comment Изменить комментарий Edit image post Изменить сообщение с изображением &Update object &Обновить объект &Update post &Обновить сообщение &Update comment &Обновить комментарий &Update image post &Обновить сообщение с изображением Select Image Выбрать Изображение Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Файлы изображений (*.png *.jpg *.jpeg *.gif);;Все файлы (*.*) Sorry! Виноват! Простите! That file didn't appear to be an image. Этот файл не является изображением. &Add picture &Добавить изображение &Change picture &Изменить изображение Characters: %1 Символов: %1 OAuthFirstPage Welcome to Pumpa! Добро пожаловать в Pumpa! <p>In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>When you are done enter your new pump.io account id below in the form of <b>username@servername</b>.</p> <p>Для того чтобы использовать pump.io вы должны сначала зарегистрировать аккаунт на pump.io сервере. Если вы ещё не сделали этого, то вы можете сделать это сейчас воспользовавшись одним из существующих публичных серверов: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>Когда вы закончите введите ваш новый идентефикатор pump.io аккаунта в форме <b>username@servername</b>.</p> <b>Your pump.io account id:</b> <b>Ваш идентификатор pump.io аккаунта:</b> Use secure connection (recommended) Использовать безопасное соединение (рекомендуется) Next Далее OAuthSecondPage Authorise Pumpa Авторизация Pumpa In order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy &amp; paste the <b>verifier</b> text string back into the field below. (The token should be automatically pre-filled.) Возможно нужно перевести слова Token и Verifier Для того чтобы Pumpa могла читать и публиковать новые сообщения в ваш pump.io аккаунт вы должны предоставить Pumpa доступ через веб страницу. Pumpa откровет веб страницу для вас - просто следуйте инструкциям и скопируйте текстовую строчку <b>verifier</b> и вставьте её в поле снизу от текста. (Token уже должен быть заполнен автоматически.) Token: Token: Verifier: Verifier: OAuthWizard Registering client ... Регистрирую клиента ... Network error: Ошибка сети: Registered client to [%1] successfully. Клиент успешно зарегистрирован на [%1]. Authorising user ... Авторизую пользователя ... Network or authentication error! Ошибка аутентификации или сети! ObjectWidget Re: Я не знаю как правильно перевести "Re:", а если его не переводить, то возможно "To:" и "Cc:" тоже стоит оставить без перевода Re: show context показать контекст PumpApp &Inbox В&ходящие &Mentions &Упоминания &Direct П&рямые Mean&while &Между тем SSL Error: Ошибка SSL: %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. %1 не в состоянии проверить подлинность сервера. Эта ошибка может значить, что кто-то пытается выдать себя за сервер, или что администратор сервера совершил ошибку. SSL Server certificate. Я не знаю правильного перевода для терминов внутри сертификата, так что переведу по смыслу. К этим местам нужно отнестись внимательно SSL сертификат Сервера Issued to: Кому выдан: Issued by: Кем выдан: Effective: Expires: Истекает: MD5 digest: MD5 сумма: <b>Untrusted SSL connection!</b> <b>Небезопасное SSL соединение!</b> Public Публично Followers Подписчики You have %Ln new notification(s). У вас %Ln новое уведомление. У вас %Ln новых уведомления. У вас %Ln новых уведомлений. %1 commented: %1 откомментировал: %1 wrote: %1 написал: Error: Ошибка: %1 &Window %1 &Окно Hide Спрятать Show Показать E&xit В&ыход Ctrl+Q Ctrl+Q Preferences Настройки &Reload timeline Переза&грузить сообщения Ctrl+R Ctrl+R Load older in timeline Загрузить более старые сообщения Ctrl+O Ctrl+O F&ollow an account &Подписаться на пользователя Ctrl+L Ctrl+L Your &profile Ваш п&рофиль &About &О программе About &Qt О &Qt Report &bug online &Сообщить об ошибке New &Note Новое &Сообщение Ctrl+N Ctrl+N Ctrl+D Ctrl+D Close tab Закрыть вкладку Ctrl+W Ctrl+W Firehose Firehose Following Возможно тут нужно использовать слово, которое сильнее визуально отличается от "подписчиков" Подписки Favorites Избранное Activities Активность &Pumpa &Pumpa &Tabs &Вкладки &Help &Помощь <p>Pumpa 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 3 of the License, or (at your option) any later version.</p><p>Pumpa 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.</p><p>You should have received a copy of the GNU General Public License along with Pumpa. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> <p>The <a href="https://github.com/kypeli/kQOAuth">kQOAuth library</a> is copyrighted by <a href="http://www.johanpaul.com/">Johan Paul</a> and licensed under LGPL 2.1.</p><p>The <a href="https://github.com/vmg/sundown">sundown Markdown library</a> is copyrighted by Natacha Port&eacute;, Vicent Marti and others, and <a href="https://github.com/vmg/sundown#license">permissively licensed</a>.</p><p>The Pumpa logo was <a href="http://opengameart.org/content/fruit-and-veggie-inventory">created by Joshua Taylor</a> for the <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0. Copyright &copy; 2013-2015 Mats Sj&ouml;berg <p>Report bugs and feature requests at <a href="%5">%5</a>.</p> <p>Сообщайте об ошибках и запрашивайте новые возможности по адресу <a href="%5">%5</a>.</p> A simple Qt-based pump.io client. Простой pump.io клиент на основе Qt About %1 О %1 Site not configured yet! Сайт ещё не настроен! Follow pump.io user Подписка на pump.io пользователя Enter webfinger ID of person to follow: Введите webfinger ID человека, на которого хотите подписаться: Sorry, that doesn't even look like a webfinger ID! Простите, это даже не похоже на webfinger ID! Sorry, you are already following that person! Простите, вы уже подписаны на этого человека! Invalid user: Некорректный пользователь: Invalid user (cannot check site): Некорректный пользователь (не могу проверить сайт): &Context &Контекст Fi&rehose Fi&rehose &Followers &Подписчики F&ollowing П&одписки F&avorites &Избранное A&ctivities &Активность Loading ... Загрузка ... Ready! Готово! Unable to post message! Не могу опубликовать сообщение! Unable to upload image! Не могу загрузить изображение! Network or authorisation error [%1/%2] %3. Ошибка сети или авторизации [%1/%2] %3. Successfully followed Успешно подписался на Successfully unfollowed Успешно отписался от PumpaSettingsDialog Account Аккаунт Not logged in currently. В данный момент не подключен. Change account Изменить аккаунт Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time. Нажатие "Изменить аккаунт" повторно запустит настройки аутентификации для нового pump.io аккаунта. В результате текущие ключи подключения будут удалены, поскольку Pumpa поддерживает одновременно только один аккаунт. Interface Возможно лучше сказать "Внешний вид" Интерфейс Update interval (in minutes): Интервал обновления (в минутах): Public Публично Followers Подписчики Default "To": "Кому" по умолчанию: Default "CC": "Копия" по умолчанию: Use icon in system tray Добавлять иконку в трей Show message character count Показывать количество символов в сообщении Notifications Уведомления Never Никогда Direct only Только прямые сообщения Direct or mention Прямые соощбения или упоминания Direct, mention or inbox Прямые сообщения, упоминания или входящие Anything Все сообщения Highlight tray icon on: Подсвечивать иконку в трее на: Popup notification on: Всплывающие уведомления на: Currently logged in as %1. В данный момент подключён как %1 QASObject Deleted %1 Удалено %1 QObject a few seconds ago несколько секунд назад one minute ago минуту назад %n minutes ago %n минуту назад %n минуты назад %n минут назад one hour ago один час назад один час назад один час назад %n hours ago %n час назад %n часа назад %n часов назад one day ago один день назад один день назад один день назад %n days ago %n день назад %n дня назад %n дней назад one week ago одну неделю назад одну неделю назад одну неделю назад %n weeks ago %n неделю назад %n недели назад %n недель назад one month ago один месяц назад один месяц назад один месяц назад %n months ago %n месяц назад %n месяца назад %n месяцев назад one year ago один год назад один год назад один год назад %n years ago %n год назад %n года назад %n лет назад pumpa-0.9.2/translations/pumpa_fi.ts0000644000175000017500000013527612653206625016254 0ustar matsmats ActivityWidget via %1 sovelluksella %1 To: To: CC: CC: Public Julkinen ActorWidget stop following lopeta seuraaminen follow seuraa stop minimising posts lopeta viestien pienentäminen auto-minimise posts pienennä viestit automaattisesti Are you sure you want to stop following %1? Haluatko lopettaa henkilön %1 seuraamisen? CollectionWidget Load older Lataa vanhemmat EditProfileDialog &Change picture Vaihda &kuva Real name Nimesi Hometown Kotipaikkasi Bio Tietoja minusta/kuvaus minusta/... Kuvaus Select Image Valitse kuva Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Kuvatiedostot (*.png, *.jpg, *.jpeg, *.gif);;Kaikki tiedostot (*.*) Sorry! Hups! That file didn't appear to be an image. Tämä tiedosto ei näyttäisi olevan kuva. FileDownloader Network error: Verkkovirhe: Network error: too many redirections! Verkkovirhe: liian monta uudelleenohjausta! Unable to download %1 (Error #%2). Tiedoston %1 lataaminen ei onnistu (virhe #%2). Could not open file %1 for writing: Tiedoston %1 avaaminen kirjoittamista varten ei onnistu: FullObjectWidget delete poista edit muokkaa [No description] [Ei kuvausta] comment Button to comment on a post vastaa at %1 location of person paikassa %1 Profile last updated %1 Profiilia päivitetty viimeksi %1 updated %1 päivitetty %1 at %1 time when post was published %1 unlike älä tykkääkään like tykkää share jaa stop following lopeta seuraaminen follow seuraa follow %1 seuraa henkilöä %1 You like this. Sinä tykkäät tästä. %1 likes this. %1 tykkää tästä. %1 like this. %1 tykkäävät tästä. and %Ln other person(s) ja yksi muu henkilö ja %Ln muuta henkilöä shared this. Many persons shared jakoivat tämän. shared this. One person shared jakoi tämän. post Name of object type. Käytetään suomennoksissa "jakaa/poistaa postin". postin note Name of object type. Käytetään suomennoksissa "jakaa/poistaa viestin". viestin comment Name of object type. Käytetään suomennoksissa "jakaa/poistaa kommentin". kommentin image Name of object type. Käytetään suomennoksissa "jakaa/poistaa kuvan". kuvan %Ln person(s) shared this. yksi henkilö jakoi tämän. %Ln henkilöä jakoivat tämän. Show all replies Näytä kaikki vastaukset Are you sure you want to delete this %1? Haluatko poistaa tämän %1? Share this %1 by %2? Haluatko jakaa henkilön %2 %1? MessageEdit Spelling suggestions... Oikeinkirjoitusehdotukset… MessageRecipients - - MessageWindow Use Markdown Käytä Markdownia [help] [ohje] To: Vastaanottajat (To): Cc: Kopiot (CC): &Remove picture Poista &kuva + &To + &To + &Cc + &Cc Title (optional) Otsikko (vapaaehtoinen) Ca&ncel &Peru &Preview &Esikatselu &Send message &Lähetä viesti Select recipient (To) Valitse vastaanottaja (To) Select recipient (Cc) Valitse vastaanottaja (CC) Post a reply Lähetä vastaus Post a note Lähetä viesti &Send comment &Lähetä vastaus &Send post &Lähetä viesti Edit object Muokkaa oliota Edit post Muokkaa viestiä Edit comment Muokkaa kommenttia Edit image post Muokaa kuvaviestiä &Update object &Päivitä objekti &Update post &Päivitä viestiä &Update comment &Päivitä kommenttia &Update image post &Päivitä kuvallista viestiä Select Image Valitse kuva Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Kuvatiedostot (*.png, *.jpg, *.jpeg, *.gif);;Kaikki tiedostot (*.*) Sorry! Hups! That file didn't appear to be an image. Tämä tiedosto ei näyttäisi olevan kuva. &Add picture Lisää &kuva &Change picture Vaihda &kuvaa Characters: %1 Merkkejä: %1 OAuthFirstPage Welcome to Pumpa! Tervetuloa käyttämään Pumpaa! <p>In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>When you are done enter your new pump.io account id below in the form of <b>username@servername</b>.</p> <p>Käyttääksesi pump.io:ta sinun pitää ensin rekisteröidä tunnus jollekin pump.io-palvelimelle. Jos et ole vielä tehnyt sitä, voit tehdä nyt jollekin olemassa olevalla palvelimelle: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>Kun olet valmis, syötä uuden pump.io-tilin tunnus alle muodossa <b>käyttäjä@palvelin</b>.</p> <b>Your pump.io account id:</b> <b>Pump.io-tilisi tunnus:</b> Use secure connection (recommended) Käytä salattua yhteyttä (suositeltu) Next Seuraava OAuthSecondPage Authorise Pumpa Valtuuta Pumpa In order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy &amp; paste the <b>verifier</b> text string back into the field below. (The token should be automatically pre-filled.) Jotta Pumpa voisi lukea ja lähettää uusia viestejä pump.io-tilillesi, sinun täytyy myöntää Pumpalle oikeus WWW-sivun kautta. Pumpa avaa sinulle oikean WWW-sivun. Seuraa sivun ohjeita ja kopioi sieltä <b>verfier</b>-teksti alla olevaan kenttään. (Token-kentän pitäisi olla automaattisesti täytetty.) Token: Token: Verifier: Verifier: OAuthWizard Registering client ... Rekisteröidään asiakasohjelmaa… Network error: Verkkovirhe: Registered client to [%1] successfully. Asiakasohjelma rekisteröity onnistuneesti palvelimelle %1. Authorising user ... Valtuutetaan käyttäjä… Network or authentication error! Verkko- tai tunnistautumisvirhe! ObjectWidget Re: Re: show context Tähän voisi yrittää keksiä kontekstia paremman suomennoksen. Ehkä keskustelu? näytä keskustelu PumpApp &Inbox &Viestit &Mentions &Maininnat &Direct V&astaanotetut Mean&while &Sillä välin SSL Error: SSL-virhe: %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. \n%1 ei pysty varmistamaan palvelimen identiteettiä. Tämä virhe voi tarkoittaa sitä, että joku yrittää tarjota väärennettyä palvelinta tai sitten palvelimen ylläpitäjä on tehty jonkin virheen. SSL Server certificate. SSL-palvelinsertifikaatti. Issued to: Kohde: Issued by: Myöntäjä: Effective: Voimassaolo alkaa: Expires: Voimassaolo päättyy: MD5 digest: MD5-tiiviste: <b>Untrusted SSL connection!</b> <b>Epäluotettava SSL-yhteys!</b> Public Julkinen Followers Seuraajat You have %Ln new notification(s). Sinulle on uusi ilmoitus. Sinulle on %Ln uutta ilmoitusta. %1 commented: %1 kommentoi: %1 wrote: %1 kirjoitti: Error: Virhe: %1 &Window %1 &ikkuna Hide Piilota Show Näytä E&xit &Lopeta Ctrl+Q Ctrl+Q Preferences Asetukset &Reload timeline Lataa &aikajana uudelleen Ctrl+R Ctrl+R Load older in timeline Lataa vanhempia viestejä Ctrl+O Ctrl+O F&ollow an account &Seuraa tiliä Ctrl+L Ctrl+L Your &profile &Profiilisi &About &Tietoja About &Qt Tietoja &Qt:stä Report &bug online Tee &vikailmoitus New &Note Uusi &viesti Ctrl+N Ctrl+N Ctrl+D Ctrl+D Close tab Sulje välilehti Ctrl+W Ctrl+W Firehose Firehose Following Seuratut Favorites Suosikit Activities Aktiviteetit &Pumpa &Pumpa &Tabs &Välilehdet &Help &Ohje <p>Pumpa 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 3 of the License, or (at your option) any later version.</p><p>Pumpa 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.</p><p>You should have received a copy of the GNU General Public License along with Pumpa. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> <p>The <a href="https://github.com/kypeli/kQOAuth">kQOAuth library</a> is copyrighted by <a href="http://www.johanpaul.com/">Johan Paul</a> and licensed under LGPL 2.1.</p><p>The <a href="https://github.com/vmg/sundown">sundown Markdown library</a> is copyrighted by Natacha Port&eacute;, Vicent Marti and others, and <a href="https://github.com/vmg/sundown#license">permissively licensed</a>.</p><p>The Pumpa logo was <a href="http://opengameart.org/content/fruit-and-veggie-inventory">created by Joshua Taylor</a> for the <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0. Copyright &copy; 2013-2015 Mats Sj&ouml;berg <p>Report bugs and feature requests at <a href="%5">%5</a>.</p> <p>Tee vikailmoitukset ja ehdotukset uusista ominaisuuksista osoitteeseen <a href="%5">%5</a>.</p> A simple Qt-based pump.io client. Yksinkertainen Qt-pohjainen pump.io-asiakasohjelma. About %1 Tietoja %1sta Site not configured yet! Sivustoa ei ole vielä määritelty! Follow pump.io user Seuraa pump.io-käyttäjää Enter webfinger ID of person to follow: Syötä seurattavan henkilön webfinger-ID: Sorry, that doesn't even look like a webfinger ID! Anteeksi, mutta se ei edes näyttänyt webfigner-ID:ltä! Sorry, you are already following that person! Anteeksi, mutta seuraat jo tätä henkilöä! Invalid user: Toimimaton tunnus: Invalid user (cannot check site): Toimimaton tunnus (sivustoa ei pystyta tarkistamaan): &Context &Keskustelu Fi&rehose Fi&rehose &Followers &Seuraajat F&ollowing S&euratut F&avorites S&uosikit A&ctivities &Aktiviteetit Loading ... Ladataan… Ready! Valmis! Unable to post message! Viestin lähettäminen ei onnistu! Unable to upload image! Kuvan lataaminen ei onnistu! Network or authorisation error [%1/%2] %3. Verkko- tai tunnistautumisvirhe [%1/%2] %3. Successfully followed Tässä olisi hyvä käyttää %1-argumentteja. Seurataan kättäjää: Successfully unfollowed Tässä olisi hyvä käyttää %1-argumentteja. Käyttäjän seuraaminen lopetettu: PumpaSettingsDialog Account Tili Not logged in currently. Ei kirjauduttu sisään. Change account Vaihda tiliä Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time. Painamalla "Vaihda tiliä" tehdään uudet tunnistautumisasetukset pump.io-tilille. Tämä poistaa nykyisen kirjautumisen tunnistautumistiedot, koska Pumpa tukee vain yhtä tiliä kerrallaan. Interface Käyttöliittymä Update interval (in minutes): Päivitysväli (minuuteja): Public Julkinen Followers Seuraajat Default "To": Oletusvastaanottajat (To): Default "CC": Oletusvastaanottajat (CC): Use icon in system tray Näytä kuvake järjestelmän ilmoitusalueella Show message character count Näytä merkkilaskuri Notifications Ilmoitukset Never Ei koskaan Direct only Vain vastaanotetusta viesteistä Direct or mention Vastaanotetuista viesteistä ja maininnoista Direct, mention or inbox Tähän pistisi keksi jokin parempi… Vastaanotetuista, maininnoista ja muista viesteistä Anything Kaikista Highlight tray icon on: Korosta ilmoitusalueen kuvake: Popup notification on: Näytä ilmoitus: Currently logged in as %1. Kirjauduttu sisään tunnuksella %1. QASObject Deleted %1 Poistettu %1 QObject a few seconds ago muutama sekunti sitten one minute ago minuutti sitten %n minutes ago minuutti sitten %n minuuttia sitten one hour ago tunti sitten %n tuntia sitten %n hours ago tunti sitten %n tuntia sitten one day ago päivä sitten %n päivää sitten %n days ago päivä sitten %n päivää sitten one week ago viikko sitten %n viikkoa sitten %n weeks ago viikko sitten %n viikkoa sitten one month ago kuukausi sitten %n kuukautta sitten %n months ago kuukausi sitten %n kuukautta sitten one year ago vuosi sitten %n vuotta sitten %n years ago vuosi sitten %n vuotta sitten pumpa-0.9.2/translations/pumpa_de.ts0000644000175000017500000014247312653206625016243 0ustar matsmats ActivityWidget via %1 über %1 To: an: CC: CC: Public Öffentlich ActorWidget stop following Nicht mehr folgen follow Folgen stop minimising posts Beiträge minimieren ausschalten auto-minimise posts Beiträge automatisch minimieren Are you sure you want to stop following %1? Sind Sie sicher, dass Sie %1 nicht mehr folgen wollen? CollectionWidget Load older Ältere Beiträge laden EditProfileDialog &Change picture Bild &ändern Real name Hometown Bio Select Image Bild auswählen Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Bilddatei (*.png *.jpg *.jpeg *.gif);;Alle Dateien (*.*) Sorry! Entschuldigung! That file didn't appear to be an image. Die ausgewählte Datei scheint kein Bild zu sein. FileDownloader Network error: Netzwerkfehler: Network error: too many redirections! Unable to download %1 (Error #%2). Download von %1 nicht möglich (Fehler #%2). Could not open file %1 for writing: Datei %1 konnte zum Schreiben nicht geöffnet werden: FullObjectWidget delete löschen edit bearbeiten comment kommentieren comment Button to comment on a post kommentieren [No description] [Keine Beschreibung] at %1 @ %1 at %1 location of person @ %1 Profile last updated %1 letzte Profiländerung am %1 updated %1 geändert %1 at %1 time when post was published @ %1 unlike unlike like like share teilen stop following enfolge ist nicht das schönste Wort. Allerdings steht hinter dem stop following direkt der Nickname als z.B. "stop following evan" entfolge follow folge shared this. Many persons shared teilten das. shared this. One person shared teilten das. Show all replies Alle Kommentare anzeigen post Name of object type. Beitrag note Name of object type. Mitteilung comment Name of object type. kommentieren image Name of object type. Bild stop following %1 %1 nicht mehr folgen follow %1 %1 folgen You like this. Dir gefällt das. %1 likes this. %1 gefällt das. %1 like this. %1 gefällt das. and %Ln other person(s) und %Ln weitere Person und %Ln weiteren Personen Are you sure you want to stop following %1? Sind Sie sicher, dass Sie %1 nicht mehr folgen wollen? %Ln persons shared this. one person shared this. %n persons shared this. shared this. teilten das. %Ln person(s) shared this. von %Ln Person geteilt von %Ln Personen geteilt Show all %1 replies alle %1 Antworten anzeigen post Beitrag note Mitteilung image Bild Are you sure you want to delete this %1? Sind Sie sicher das sie %1 löschen wollen? Share this %1 by %2? %1 von %2 teilen? Are you sure you want to stop following Sind Sie sicher, dass Sie dem Benutzer nicht mehr folgen wollen MessageEdit Spelling suggestions... Rechtschreibvorschläge ... MessageRecipients - - MessageWindow Use Markdown Markdown verwenden [help] [Hilfe] To: To: Cc: Cc: &Remove picture Bild &löschen + &To + &To + &Cc + &Cc Title (optional) Titel (optional) Ca&ncel Abbreche&n &Preview &Vorschau &Send message Nachricht &senden &Send comment Kommentar &senden &Send post Beitrag &senden Edit object Objekt bearbeiten Edit post Beitrag bearbeiten Edit comment Kommentar bearbeiten Edit image post Bildnachricht bearbeiten &Update object Objekt akt&ualisieren &Update post Nachricht akt&ualisieren &Update comment Kommentar akt&ualisieren &Update image post Bildnachricht akt&ualisieren Characters: %1 Cancel Abbrechen Preview Vorschau Send message Mitteilung senden Select recipient (To) Empfänger auswählen (To) Select recipient (Cc) Empfänger auswählen (Cc) Post a note Neue Mitteilung Post a reply Antwort verfassen Mentions: Vorschläge: Select Image Bild auswählen Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Bilddatei (*.png *.jpg *.jpeg *.gif);;Alle Dateien (*.*) Sorry! Entschuldigung! That file didn't appear to be an image. Die ausgewählte Datei scheint kein Bild zu sein. &Add picture &Bild einfügen &Change picture Bild &ändern OAuthFirstPage Welcome to Pumpa! Willkommen zu Pumpa! <p>In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>When you are done enter your new pump.io account id below in the form of <b>username@servername</b>.</p> <p>Um pump.io nutzen zu können, benötigen Sie einen Account auf einem pump.io-Server. Wenn Sie noch keinen Account besitzen, können Sie sich einfach an einem der öffentlichen pump.io-Server unter folgendem Link registrieren:</p> <a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a><p>Wenn Sie einen Account erstellt haben oder bereits ein pump.io-Konto besitzen, tragen Sie Ihre Account-ID in dem Format <b>benutzername@servername</b> in dem unten angezeigten Feld ein.</p> <b>Your pump.io account id:</b> <b>Ihre pump.io Account-ID:</b> Use secure connection (recommended) Sichere Verbindung verwenden (empfohlen) Next "Back" ist leider nicht vorhanden. Daher macht es keinen Sinn nur Next mit Vor zu übersetzen OAuthSecondPage Authorise Pumpa Pumpa autorisieren In order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy &amp; paste the <b>verifier</b> text string back into the field below. (The token should be automatically pre-filled.) Damit Pumpa neue Nachrichten lesen und verfassen kann, müssen Sie den Zugriff auf Ihren pump.io-Account gestatten. Pumpa wird dazu eine Webseite des pump.io-Servers aufrufen, deren Anweisungen Sie folgen. Um den Anmeldevorgang abzuschließen, kopieren Sie den <b>Verifier</b> in das obere Feld. (Der Token sollte von Pumpa automatisch ausgefüllt werden, sobald sich die Webseite öffenet) Token: Token: Verifier: Verifier: OAuthWizard Registering client ... Client regestrieren ... Network error: Netzwerkfehler: Registered client to [%1] successfully. Anmeldung des Clients an [%1] erfolgreich. Authorising user ... Autorisiere Benutzer ... Network or authentication error! Netzwerk- oder Autorisierungsfehler! ObjectWidget Re: Re: show context Kontext anzeigen PumpApp &Inbox &Nachrichten &Mentions &Vorschläge &Direct &Posteingang Mean&while N&euigkeiten You have %Ln new notification(s). Copyright &copy; 2013-2015 Mats Sj&ouml;berg Fi&rehose Fi&rehose Error: Fehler: %1 &Window %1 Fenster Hide Verstecken Show Anzeigen E&xit E&xit Ctrl+Q Strg+X Preferences Eigenschaften &Reload timeline Beit&räge neu laden Ctrl+R Strg+R Load older in timeline Ältere Beiträge laden Ctrl+O Strg+L F&ollow an account Nutzer &folgen Ctrl+L Strg+F &About Über About &Qt Über &Qt New &Note &Neue Mitteilung Ctrl+N Strg+N Ctrl+D Strg+D Followers Followers SSL Error: SSL Fehler: %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. %1 ist nicht in der Lage, die Identität des Servers zu überprüfen. Dieser Fehler könnte bedeuten, dass jemand versucht sich als dieser Server auszugeben, oder dass der Server-Administrator einen Fehler gemacht hat. SSL Server certificate. SSL Server Zertifkat. Issued to: Ausgestellt für: Issued by: Herausgegeben von: Effective: Effektiv: Expires: Gültig bis: MD5 digest: MD5-Digest: <b>Untrusted SSL connection!</b> <b>Nicht vertrauenswürdige SSL-Verbindung!</b> Public Öffentlich You have %1 new notifications. Sie habe %1 neue Benachrichtigung. %1 commented: %1 kommentierte: %1 wrote: %1 schreibt: Your &profile Report &bug online Close tab Ctrl+W Firehose Following Favorites Favoriten Activities Aktivitäten &Pumpa &Tabs &Help &Hilfe <p>Pumpa 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 3 of the License, or (at your option) any later version.</p><p>Pumpa 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.</p><p>You should have received a copy of the GNU General Public License along with Pumpa. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> <p>The <a href="https://github.com/kypeli/kQOAuth">kQOAuth library</a> is copyrighted by <a href="http://www.johanpaul.com/">Johan Paul</a> and licensed under LGPL 2.1.</p><p>The <a href="https://github.com/vmg/sundown">sundown Markdown library</a> is copyrighted by Natacha Port&eacute;, Vicent Marti and others, and <a href="https://github.com/vmg/sundown#license">permissively licensed</a>.</p><p>The Pumpa logo was <a href="http://opengameart.org/content/fruit-and-veggie-inventory">created by Joshua Taylor</a> for the <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0. <p>Report bugs and feature requests at <a href="%5">%5</a>.</p> <p> Fehler und Verbesserungsvorschläge bitte an <a href="%5">%5</a> melden!</p> A simple Qt-based pump.io client. Ein einfacher Qt-basierter pump.io-Client. About %1 Über %1 Site not configured yet! Seite ist nicht konfiguriert! Follow pump.io user pump.io Nutzer folgen Enter webfinger ID of person to follow: Geben Sie die Webfinger-ID der Person ein, der Sie folgen wollen: Sorry, that doesn't even look like a webfinger ID! Entschuldigung, aber das sieht leider nicht nach einer Webfinger-ID aus! Sorry, you are already following that person! Entschuldigung! Sie folgen dieser Person bereits! Invalid user: Ungültiger Benutzer: Invalid user (cannot check site): Ungültiger Benutzer (Seite kann nicht überprüft werden): &Context Kontext &Followers F&ollowing F&avorites F&avoriten A&ctivities Aktivitäten Loading ... Lade ... Ready! Fertig! Unable to post message! Nachricht kann nicht veröffentlicht werden! Unable to upload image! Bild kann nicht hochgeladen werden! Network or authorisation error [%1/%2] %3. Netzwerk- oder Autorisierungsfehler! [%1/%2] %3. Successfully followed Du folgst jetzt Successfully unfollowed Entfolgen erfolgreich PumpaSettingsDialog Account Konto Not logged in currently. Zur Zeit nicht eingeloggt. Change account Konto ändern Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time. Wenn sie auf "Konto ändern" klicken, wird der Authentifizierungsprozess neu gestartet. Alle Anmeldedaten zu Ihrem pump.io-Account werden vom System entfernt. Mit Pumpa kann zur Zeit nur ein Account zur selben Zeit genutzt werden. Interface Schnittstelle Update interval (in minutes): Aktualisierungsintervall (in Minuten): Public Öffentlich Followers Default "To": Standardempfänger für "To": Default "CC": Standardempfänger für "CC": Use icon in system tray Icon in der Statusleiste anzeigen Show message character count Notifications Benachrichtigungen Never Niemals Direct only Nur direkt Direct or mention Direkt oder mit Vorschlägen Direct, mention or inbox Direkt, mit Vorschlägen oder aus dem Posteingang Anything Irgendetwas Highlight tray icon on: Icon in der Statusleiste hervorheben wenn: Popup notification on: Popup-Benachrichtigungen bei: Currently logged in as %1. Zur Zeit eingeloggt als: %1. QASObject Deleted %1 Gelöscht am %1 QObject a few seconds ago vor ein paar Sekunden one minute ago vor einer Minute %n minutes ago vor %n Minute vor %n Minuten one hour ago vor einer Stunde vor einer Stunde %n hours ago vor %n Stunden vor %n Stunden one day ago vor einem Tag vor einem Tag %n days ago vor %n Tagen vor %n Tagen one week ago vor einer Woche vor einer Woche %n weeks ago vor %n Wochen vor %n Wochen one month ago vor einem Monat vor einem Monat %n months ago vor %n Monaten vor %n Monaten one year ago vor einem Jahr vor einem Jahr %n years ago vor %n Jahren vor %n Jahren %n minute(s) ago vor %n Minute vor %n Minuten %n hour(s) ago vor %n Stunde vor %n Stunden pumpa-0.9.2/translations/pumpa_nvi.qm0000644000175000017500000003656012653206625016435 0ustar matsmatsPump.io ngey account tstxo:</b>Your pump.io account id:OAuthFirstPage"<p>Nga kin ngivop accountit fte sar pumpit. Txo nga ngivop mi fwa frapori ngal tsun fmivi pumpit pumpro. <br />Rutxe k <a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>ur.</p><p>Rutxe pamrel si ngey tstxo apump mfa, sweylu txo livu <b>tstxo@tseng</b></p>p

      In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers:
      http://pump.io/tryit.html.

      When you are done enter your new pump.io account id below in the form of username@servername.

      OAuthFirstPageHayNextOAuthFirstPage@Hawnu ngey tpngkxoti pumpahu #Use secure connection (recommended)OAuthFirstPage.Ziva'u nprrte Pumparu!Welcome to Pumpa!OAuthFirstPageTung pumpatiAuthorise PumpaOAuthSecondPageNgal kin tivung pumpati fte pumpal tsun pivlltxe pumpur. Pumpa wayntxu pageit ngaru. Nga pamrel si pumparu fwa <b>verifier</b>.HIn order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy & paste the verifier text string back into the field below. (The token should be automatically pre-filled.)OAuthSecondPage Tokn:Token:OAuthSecondPageVerifier: Verifier:OAuthSecondPage&Steftxaw tuteti ...Authorising user ... OAuthWizard&Tngzk apuslltxe:Network error:  OAuthWizard<Puslltxe fu steftxaw tngzk! Network or authentication error! OAuthWizard:Pumpl kerame fpumpati [%1].'Registered client to [%1] successfully. OAuthWizardNPawm futa pumpl kyevame fpumpati ...Registering client ... OAuthWizard 'Eyng:Re:  ObjectWidgettse'a 'upxareti show context ObjectWidget%1 ke tsun stiveftxaw pumpur. Kxawm kxutul tse'a tpngkxoti ngay hu pump fu tngzk lu latemyuru. %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. PumpApp%1 &Wntow %1 &WindowPumpApp%1 pamrel si:%1 commented: PumpApp%1 pamrel si: %1 wrote: PumpApp&Pumpateri&AboutPumpApp&Upxare&ContextPumpApp &Oeri&DirectPumpApp&&Nga sunu Faysuteru &FollowersPumpApp &Srung&HelpPumpApp&Payfya&InboxPumpApp&Oeteri &MentionsPumpApp &Pumpa&PumpaPumpApp&Nn payfya&Reload timelinePumpApp:<b>Oel ke omum hawnuyuti!</b> Untrusted SSL connection!PumpApp<Pumpri pumpa fkeytok fte sar.!A simple Qt-based pump.io client.PumpAppTeri %1About %1PumpApp&Qtteri About &QtPumpApp|Copyright &copy; 2013 Mats Sj&ouml;berg {2014 ?} {2013-2015 ?},Copyright © 2013-2015 Mats SjöbergPumpAppCtrl+DPumpApp Ctrl+LCtrl+LPumpApp Ctrl+NCtrl+NPumpApp Ctrl+OCtrl+OPumpApp Ctrl+QCtrl+QPumpApp Ctrl+RCtrl+RPumpApp&HumE&xitPumpAppLPamrel si tstxour apump fte 'eylan si:(Enter webfinger ID of person to follow: PumpAppTngzk:Error: PumpApp$&Tsay'u sunu ngaru F&avoritesPumpApp&'eylan siF&ollow an accountPumpApp &Eylan F&ollowingPumpApp"Tsay'u sunu ngaru FavoritesPumpApp&Payppe Fi&rehosePumpAppPayppeFirehosePumpApp 'eylan si tuteruFollow pump.io userPumpApp$Nga sunu Faysuteru FollowersPumpApp Eylan FollowingPumpAppWanHidePumpAppLTute ke lu eyawr (ke steftxaw pumpit):"Invalid user (cannot check site): PumpAppTute lu eyawr:Invalid user: PumpAppNgopyu: Issued by: PumpApp Telyu: Issued to: PumpApp Nn 'upxare ahamLoad older in timelinePumpAppNern ... Loading ...PumpAppLef&krr Mean&whilePumpAppOel ke tsun pivngkxo hu pump fu pumpl ke omum ngati [%1/%2] %3.*Network or authorisation error [%1/%2] %3.PumpApp&Ngop 'upxareti New &NotePumpAppOey ayTlatem PreferencesPumpApp FrapoPublicPumpAppTam!Ready!PumpApp"Thawnu Tngzk: SSL Error: PumpAppHThawnu Tngzk (SSLa certificate).SSL Server certificate. PumpApp WntxuShowPumpApp0Ftseng ke lu alaksi mi!Site not configured yet!PumpApp>Ngaytxoa, tsatstxo ke lu tstxo!2Sorry, that doesn't even look like a webfinger ID!PumpAppJNgaytxoa, tsasute lu ngey 'eylan li!-Sorry, you are already following that person!PumpApp'eylan soli Successfully followed PumpApp&Ke 'eylan seri setSuccessfully unfollowed PumpApp.Ke tsun fpe' 'upxareti!Unable to post message!PumpAppAccountAccountPumpaSettingsDialog'uoAnythingPumpaSettingsDialogLatem accountitChange accountPumpaSettingsDialogClerk san Latem accountit sik fte fpxkm accounthu amip. Faccount fkem 'ayaku ngey snumeti, Pumpa tsun sivar 'awa accountit.Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time.PumpaSettingsDialog(Nga fpxolkm na %1.Currently logged in as %1.PumpaSettingsDialogOeri n'aw Direct onlyPumpaSettingsDialogOeri fu oeteriDirect or mentionPumpaSettingsDialog,Oeri, oeteri fu payfyaDirect, mention or inboxPumpaSettingsDialog$Nga sunu faysuteru FollowersPumpaSettingsDialog.Atan si relur tray m:Highlight tray icon on:PumpaSettingsDialogTseng fte tse'a InterfacePumpaSettingsDialog KawkrrNeverPumpaSettingsDialogKe fpxkm mi.Not logged in currently.PumpaSettingsDialogayu amip NotificationsPumpaSettingsDialogNgop wntow m:Popup notification on:PumpaSettingsDialog FrapoPublicPumpaSettingsDialogDPehrr a nn payfyati (m aymnut):Update interval (in minutes):PumpaSettingsDialogBFelturi lefngap tray sar relit Use icon in system trayPumpaSettingsDialogseta few seconds agoQObject'aw mnut kamone minute agoQObjectpumpa-0.9.2/translations/pumpa_en.qm0000644000175000017500000000027212653206625016232 0ustar matsmats ActivityWidget via %1 vía %1 To: Para: CC: CC: Public Público ActorWidget stop following dejar de seguir follow seguir stop minimising posts dejar de minimizar envíos auto-minimise posts auto-minimizar envíos Are you sure you want to stop following %1? ¿Seguro que deseas dejar de seguir a %1? CollectionWidget Load older Cargar antiguos EditProfileDialog &Change picture &Cambiar imagen Real name Nombre real Hometown Ciudad Bio Bio Select Image Seleccionar imagen Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Archivos de imagen (*.png *.jpg *.jpeg *.gif);;Todos los archivos (*.*) Sorry! ¡Lo siento! That file didn't appear to be an image. Ese archivo no parece una imagen. FileDownloader Network error: Error de red: Network error: too many redirections! Error de red: ¡demasiadas redirecciones! Unable to download %1 (Error #%2). No se pudo descargar %1 (Error #%2). Could not open file %1 for writing: No se pudo abrir el fichero %1 para escritura: FullObjectWidget delete borrar edit editar comment comentar comment Button to comment on a post comentar [No description] [Sin descripción] at %1 at en %1 at %1 location of person en %1 Profile last updated %1 Última actualización del perfil: %1 updated %1 actualizado: %1 at %1 time when post was published el %1 unlike no me gusta like me gusta share compartir stop following dejar de seguir a follow seguir a shared this. Many persons shared compartieron esto. shared this. One person shared compartió esto. Show all replies Mostrar todas las respuestas post Name of object type. la publicación note Name of object type. la nota comment Name of object type. el comentario image Name of object type. la imagen stop following %1 dejar de seguir a %1 follow %1 seguir a %1 You like this. Te gusta esto. and %Ln other person(s) y %n persona más y %n personas más %Ln person(s) shared this. %n persona compartió esto. %n personas compartieron esto. Are you sure you want to stop following %1? ¿Seguro que deseas dejar de seguir a %1? likes this. marcó «me gusta». %1 like this. like this. A %1 les gusta. %1 likes this. A %1 le gusta esto. and 1 other person y otra persona más and %1 other persons y %1 personas más shared this. compartió/compartieron esto. 1 person shared this. 1 persona compartió esto. %1 persons shared this. %1 personas compartieron esto. Show all %1 replies Mostrar todas las %1 respuestas post la publicación note la nota image la imagen Are you sure you want to delete this %1? ¿Estás seguro de que quieres borrar %1? Share this %1 by %2? ¿Compartir %1 de %2? Are you sure you want to stop following ¿Estás seguro de que quieres dejar de seguirle? MessageEdit Spelling suggestions... Sugerencias de ortografía... MessageRecipients - - MessageWindow [markup] [marcado] Public Público Followers Seguidores To: Para: Cc: Cc: &Remove picture Borra&r imagen + &To + &Para + &Cc + &Cc Picture title (optional) Título de la imagen (opcional) Use Markdown Usar Markdown [help] [ayuda] Title (optional) Título (opcional) Cancel Cancelar Preview Previsualizar Send message Enviar mensaje Select recipient (To) Selecciona destinatario (Para) Select recipient (Cc) Selecciona destinatario (Cc) Post a note Publicar una nota Post a reply Publicar una respuesta Mentions: Menciones: Ca&ncel Ca&ncelar &Preview &Previsualizar &Send message &Enviar mensaje &Send comment &Enviar comentario &Send post &Enviar Edit object Editar objeto Edit post Editar Edit comment Editar comentario Edit image post Editar imagen &Update object &Actualizar objeto &Update post &Actualizar envío &Update comment &Actualizar comentario &Update image post &Actualizar imagen Select Image Seleccionar imagen Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Archivos de imagen (*.png *.jpg *.jpeg *.gif);;Todos los archivos (*.*) Sorry! ¡Lo siento! That file didn't appear to be an image. Ese archivo no parece una imagen. &Add picture &Agregar imagen &Change picture &Cambiar imagen Characters: %1 Caracteres %1 OAuthFirstPage Welcome to Pumpa! ¡Bienvenido a Pumpa! <p>In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>When you are done enter your new pump.io account id below in the form of <b>username@servername</b>.</p> <p>Para poder usar pump.io, primero necesitas registrar una cuenta en un servidor pump.io. Si aún no lo has hecho, puedes hacerlo ahora en uno de los servidores públicos existentes: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>Cuando la tengas, introduce tu identificador de cuenta de pump.io en el formulario de abajo, en la forma <b>nombredeusuario@nombredeservidor</b>.</p> <b>Your pump.io account id:</b> <b>Tu identificador de cuenta en pump.io:</b> Use secure connection (recommended) Usar conexión segura (recomendado) Next Siguiente OAuthSecondPage Authorise Pumpa Autorizar Pumpa In order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy &amp; paste the <b>verifier</b> text string back into the field below. (The token should be automatically pre-filled.) Para que Pumpa pueda leer y enviar mensajes en tu cuenta de pump.io, necesitas proporcionar acceso a Pumpa a través del sitio web. Pumpa abrirá la página web para tí: simplemente sigue las instrucciones y copia y pega el texto <b>verificador</b> en el campo de más abajo. (El 'token' debería rellenarse automáticamente.) Token: Token: Verifier: Verificador: OAuthWizard Registering client ... Registrando el cliente... Network error: Error de red: Registered client to [%1] successfully. Cliente registrado en [%1] correctamente. Authorising user ... Autorizando usuario... Network or authentication error! ¡Error de red o de autenticación! ObjectWidget Re: Re: show context mostrar contexto PumpApp &Inbox Bandeja de &entrada &Mentions &Menciones &Direct &Directos Mean&while Mientras &tanto Copyright &copy; 2013 Mats Sj&ouml;berg Copyright &copy; 2013 Mats Sj&ouml;berg Fi&rehose Mangue&ra &Followers &Seguidores F&ollowing S&iguiendo You have new messages. Tienes nuevos mensajes. You have %n new notification(s). Tienes %n nueva notificación. Tienes %n nuevas notificaciones. You have %Ln new notification(s). Tienes %n nueva notificación. Tienes %n nuevas notificaciones. Error: Error: %1 &Window %1 &Ventana Hide Ocultar Show Mostrar E&xit &Salir Ctrl+Q Ctrl+Q Preferences Preferencias &Reload timeline &Recargar línea temporal Ctrl+R Ctrl+R Load older in timeline Cargar antiguos en línea temporal Ctrl+O Ctrl+O F&ollow an account Segu&ir una cuenta Ctrl+L Ctrl+L Your &profile Tu &perfil &About &Acerca de About &Qt Acerca de &Qt New &Note Nueva &Nota Ctrl+N Ctrl+N Ctrl+D Ctrl+D Followers Seguidores SSL Error: Error SSL: %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. %1 no puede verificar la identidad del servidor. Este error puede significar que alguien está tratando de suplantar al servidor, o que el administrador del servidor ha cometido un error. SSL Server certificate. Certificado SSL del servidor. Issued to: Emitido para: Issued by: Emitido por: Effective: Validez: Expires: Caduca el: MD5 digest: Huella digital MD5: <b>Untrusted SSL connection!</b> <b>¡Conexión SSL no confiable!</b> Public Público You have %1 new notifications. Tienes %1 notificaciones nuevas. %1 commented: %1 comentó: %1 wrote: %1 escribió: Report &bug online Informar so&bre un fallo Close tab Cerrar pestaña Ctrl+W Ctrl+W Firehose Manguera Following Siguiendo Favorites Favoritos Activities Actividades &Pumpa &Pumpa &Tabs Pes&tañas &Help &Ayuda <p>Pumpa 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 3 of the License, or (at your option) any later version.</p><p>Pumpa 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.</p><p>You should have received a copy of the GNU General Public License along with Pumpa. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> <p>Pumpa es software libre: puedes distribuirlo y/o modificarlo bajo los términos de la licencia GNU General Public License tal y como está publicada por la Free Software Foundation, ya sea en la versión 3 de la Licencia, o, si optas por ello, cualquier otra versión posterior.</p><p>Pumpa se distribuye con la esperanza de que resulte útil, pero SIN GARANTÍA ALGUNA, ni siquiera la garantía que implica su COMERCIABILIDAD o ADECUIDAD PARA UN PROPÓSITO PARTICULAR. Vea la licencia GNU General Public License para más detalles.</p><p>Debería haber recibido una copia de la licencia GNU General Public License junto con Pumpa. Si no es así, vea <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> <p>The <a href="https://github.com/kypeli/kQOAuth">kQOAuth library</a> is copyrighted by <a href="http://www.johanpaul.com/">Johan Paul</a> and licensed under LGPL 2.1.</p><p>The <a href="https://github.com/vmg/sundown">sundown Markdown library</a> is copyrighted by Natacha Port&eacute;, Vicent Marti and others, and <a href="https://github.com/vmg/sundown#license">permissively licensed</a>.</p><p>The Pumpa logo was <a href="http://opengameart.org/content/fruit-and-veggie-inventory">created by Joshua Taylor</a> for the <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0. <p>La <a href="https://github.com/kypeli/kQOAuth">biblioteca kQOAuth</a> está bajo derechos de autor de <a href="http://www.johanpaul.com/">Johan Paul</a> y licenciada bajo LGPL 2.1.</p><p>La <a href="https://github.com/vmg/sundown">biblioteca de Markdown sundown</a> está bajo derechos de autor de Natacha Port&eacute;, Vicent Marti y otros, y tiene <a href="https://github.com/vmg/sundown#license">licencia permisiva</a>.</p><p>El logo de Pumpa fue <a href="http://opengameart.org/content/fruit-and-veggie-inventory">creado por Joshua Taylor</a> para <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>. El logo tiene derechos de autor del artista y está licenciado dualmente bajo la licencia CC-BY-SA 3.0 y la licencia GNU GPL 3.0. Copyright &copy; 2013-2015 Mats Sj&ouml;berg Copyright &copy; 2013-2015 Mats Sj&ouml;berg Copyright &copy; 2014 Mats Sj&ouml;berg Copyright &copy; 2013 Mats Sj&ouml;berg {2014 ?} Copyright &copy; 2013 Mats Sj&ouml;berg.</p> Copyright &copy; 2013 Mats Sj&ouml;berg.</p> <p>Report bugs and feature requests at <a href="%5">%5</a>.</p> <p>Informes de error y solicitudes de características en <a href="%5">%5</a>.</p> A simple Qt-based pump.io client. Un cliente pump.io sencillo basado en Qt. About %1 Acerca de %1 Site not configured yet! ¡Sitio aún no configurado! Follow pump.io user Seguir a un usuario pump.io Enter webfinger ID of person to follow: Introduce el webfinger ID de la persona a seguir: Sorry, that doesn't even look like a webfinger ID! ¡Lo siento, eso no parece un webfinger ID! Sorry, you are already following that person! Lo siento, ¡ya sigues a esa persona! Invalid user: Usuario no válido: Invalid user (cannot check site): Usuario no válido (no se puede comprobar el sitio): &Context &Contexto F&avorites F&avoritos A&ctivities A&ctividades Loading ... Cargando ... Ready! ¡Listo! Unable to post message! ¡No se ha podido publicar el mensaje! Unable to upload image! ¡No se ha podido cargar la imagen! Network or authorisation error [%1/%2] %3. Error de red o de autorización [%1/%2] %3. Network or authorisation error [%1/%2]. Error de red o de autorización [%1/%2]. Successfully followed Siguiendo correctamente a Successfully unfollowed Dejaste correctamente de seguir a PumpaSettingsDialog Account Cuenta Not logged in currently. No se ha iniciado sesión. Change account Cambiar cuenta Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time. Hacer clic en "Cambiar cuenta" lanzará la autenticación para una nueva cuenta de pump.io. Esto eliminará las credenciales actuales ya que Pumpa sólo permite una cuenta. Interface Interfaz Update interval (in minutes): Intervalo de actualización (en minutos): Public Público Followers Seguidores Default recipient: Destinatario predeterminado: Default "To": "Para" predeterminado: Default "CC": "CC" predeterminado: Use icon in system tray Usar icono en la bandeja de sistema Show message character count Mostrar la cuenta de caracteres del mensaje Notifications Notificaciones Never Nunca Direct only Sólo directos Direct or mention Directos o menciones Direct, mention or inbox Directos, menciones o bandeja de entrada Anything Cualquier actividad Highlight tray icon on: Resaltar el icono de la bandeja en: Popup notification on: Notificación emergente en: Currently logged in as %1. Se ha iniciado sesión como %1. QASObject Deleted %1 Borrado %1 QObject a few seconds ago hace unos segundos one minute ago hace un minuto %n minutes ago hace %n minuto hace %n minutos one hour ago hace una hora hace %n horas %n hours ago hace %n hora hace %n horas one day ago hace un día hace %n días %n days ago hace %n día hace %n días one week ago hace una semana hace %n semanas %n weeks ago hace %n semana hace %n semanas one month ago hace un mes hace %n meses %n months ago hace %n mes hace %n meses one year ago hace un año hace %n años %n years ago hace %n años hace %n años %n minute(s) ago hace %n minuto hace %n minutos %n hour(s) ago hace %n hora hace %n horas pumpa-0.9.2/translations/pumpa_it.ts0000644000175000017500000014660712653206625016272 0ustar matsmats ActivityWidget via %1 via %1 To: A: CC: CC: Public Pubblico ActorWidget stop following smetti di seguire follow segui stop minimising posts non minimizzare più gli elementi auto-minimise posts minimizza automaticamente gli elementi Are you sure you want to stop following %1? Sei sicuro di voler smettere di seguire %1? CollectionWidget Load older Carica meno recenti EditProfileDialog &Change picture Ca&mbia immagine Real name Hometown Bio Select Image Seleziona un'immagine Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Files immagine (*.png *.jpg *.jpeg *.gif);;Tutti i files (*.*) Sorry! Scusa! That file didn't appear to be an image. Questo file non sembra essere un'immagine. FileDownloader Network error: Errore di rete: Network error: too many redirections! Unable to download %1 (Error #%2). Non riesco a scaricare %1 (Errore #%2). Could not open file %1 for writing: Non riesco ad aprire il file %1 in scrittura: FullObjectWidget delete elimina edit modifica comment commenta comment Button to comment on a post commenta [No description] [Nessuna descrizione] at %1 in %1 at %1 location of person in %1 Profile last updated %1 Ultimo aggiornamento profilo %1 updated %1 aggiornato %1 at %1 time when post was published in %1 unlike non mi piace like mi piace share condividi stop following smetti di seguire follow segui shared this. Many persons shared condiviso questo elemento. shared this. One person shared condiviso questo elemento. Show all replies Mostra tutte le risposte post Name of object type. messaggio note Name of object type. nota comment Name of object type. commenta image Name of object type. immagine stop following %1 smette di seguire %1 follow %1 segue %1 You like this. Ti piace. %1 likes this. A %1 piace questo elemento. %1 like this. A %1 piace questo elemento. and %Ln other person(s) e %n un'altra persona e %n altre persone Are you sure you want to stop following %1? Sei sicuro di voler smettere di seguire %1? %Ln persons shared this. one person shared this. %n persons shared this. shared this. condiviso questo elemento. %Ln person(s) shared this. %n persona ha condiviso questo elemento. %n persone hanno condiviso questo elemento. Show all %1 replies Mostra tutte le %1 risposte post messaggio note nota image immagine Are you sure you want to delete this %1? Sei sicure di volere eliminare questo %1? Share this %1 by %2? Condividi questo %1 via %2? Are you sure you want to stop following Sei sicuro di voler smettere di seguire MessageEdit Spelling suggestions... Suggerimenti ortografici... MessageRecipients - - MessageWindow [markup] [markup] To: A: Cc: Cc: &Remove picture &Rimuovi immagine + &To + &A + &Cc + &Cc Picture title (optional) Titolo immagine (opzionale) Use Markdown Usa Markdown [help] [aiuto] Title (optional) Titolo (opzionale) Cancel Cancella Preview Anteprima Send message Invia messaggio Select recipient (To) Seleziona destinatario (A) Select recipient (Cc) Seleziona destinatario (Cc) Post a note Pubblica una nota Post a reply Pubblica una risposta Mentions: Menzioni: Ca&ncel A&nnulla &Preview Ante&prima &Send message Invia me&ssaggio &Send comment Invia &commento &Send post Invia po&st Edit object Modifica oggetto Edit post Modifica post Edit comment Modifica commento Edit image post Modifica post immagine &Update object Aggiorna &oggetto &Update post Aggiorna p&ost &Update comment Aggiorna c&ommento &Update image post Aggiorna p&ost immagine Select Image Seleziona un'immagine Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Files immagine (*.png *.jpg *.jpeg *.gif);;Tutti i files (*.*) Sorry! Scusa! That file didn't appear to be an image. Questo file non sembra essere un'immagine. &Add picture A&ggiungi immagine &Change picture Ca&mbia immagine Characters: %1 OAuthFirstPage Welcome to Pumpa! Benvenuti su Pumpa! <p>In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>When you are done enter your new pump.io account id below in the form of <b>username@servername</b>.</p> <p>Per utilizzare pump.io devi registrare un account su un server pump.io. Se non ne hai ancora uno, puoi ottenerlo visitando uno dei server pubblici esistenti: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p> <p>Quando hai fatto, inserisci il tuo id dell'account pump.io, scritto come <b>nomeutente@server</b>.</p> <b>Your pump.io account id:</b> <b>L'id del tuo account pump.io</b> Use secure connection (recommended) Usa connessione sicura (raccomandato) Next Avanti OAuthSecondPage Authorise Pumpa Autorizza Pumpa In order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy &amp; paste the <b>verifier</b> text string back into the field below. (The token should be automatically pre-filled.) Per far si che Pumpa sia in grado di leggere e postare nuovi messaggi sul tuo account pump.io, devi autorizzare Pumpa attraverso una pagina web. Pumpa aprirà questa pagina per te - segui le istruzioni e copia &amp; incolla il codice di <b>verifica</b> (verifier), nel campo qui sotto (il Token dovrebbe essere automaticamente precompilato) Token: Token: Verifier: Verifier: OAuthWizard Registering client ... Registrazione del client ... Network error: Errore di rete: Registered client to [%1] successfully. Client registrato in [%1] correttamente. Authorising user ... Autorizzando l'utente ... Network or authentication error! Errore di rete o di autenticazione! ObjectWidget Re: R: show context mostra contesto PumpApp &Inbox Messagg&i ricevuti &Mentions &Menzioni &Direct &Diretti Mean&while &Nel frattempo Copyright &copy; 2013 Mats Sj&ouml;berg Copyright &copy; 2013 Mats Sj&ouml;berg Fi&rehose Fi&rehose You have new messages. Hai ricevuto nuovi messaggi. Error: Errore: %1 &Window %1 &Finestra Hide Nascondi Show Mostra E&xit E&sci Ctrl+Q Ctrl+Q Preferences Preferenze &Reload timeline &Ricarica timeline Ctrl+R Ctrl+R Load older in timeline Carica elementi più vecchi nella timeline Ctrl+O Ctrl+O F&ollow an account Segui un'acc&ount Ctrl+L Ctrl+L &About A p&roposito About &Qt A proposito di &Qt New &Note &Nuova Nota Ctrl+N Ctrl+N Ctrl+D Ctrl+D Followers Followers SSL Error: Errore SSL: %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. %1 non è in grado di verificare l'identità del server. Questo errore può significare che qualcuno sta provando a sostituire l'identità del server, o che l'amministratore del server ha commesso qualche errore. SSL Server certificate. Certificato Server SSL. Issued to: Rilasciato a: Issued by: Rilasciato da: Effective: Effettivo: Expires: Scade: MD5 digest: Digest MD5: <b>Untrusted SSL connection!</b> <b>Connessione SSL non sicura!</b> Public Pubblico You have %1 new notifications. Hai %1 nuove notifiche. %1 commented: %1 ha commentato: %1 wrote: %1 ha scritto: Report &bug online Ctrl+W Ctrl+W Firehose Firehose Following Following Favorites Preferiti Activities Attività &Pumpa &Pumpa &Tabs &Tabs &Help &Aiuto <p>Pumpa 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 3 of the License, or (at your option) any later version.</p><p>Pumpa 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.</p><p>You should have received a copy of the GNU General Public License along with Pumpa. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> <p>Pumpa è free software: puoi redistribuirlo e/o modificarlo nei termini della licenza GNU General Public License come pubblicato dalla Free Software Foundation, nella versione 3 della licenza o (a tua scelta) una versione successiva.</p><p>Pumpa è distribuito nella speranza che ti sia utile, ma SENZA NESSUNA GARANZIA; senza nessuna implicita garanzia di COMMERCIABILITA' o di ADEGUATEZZA PER UN PARTICOLARE SCOPO. Vedere la GNU General Public License per più dettagli.</p><p>Dovresti aver ricevuto una copia della licenza GNU GPL con Pumpa. In caso contrario, visita <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> <p>The <a href="https://github.com/kypeli/kQOAuth">kQOAuth library</a> is copyrighted by <a href="http://www.johanpaul.com/">Johan Paul</a> and licensed under LGPL 2.1.</p><p>The <a href="https://github.com/vmg/sundown">sundown Markdown library</a> is copyrighted by Natacha Port&eacute;, Vicent Marti and others, and <a href="https://github.com/vmg/sundown#license">permissively licensed</a>.</p><p>The Pumpa logo was <a href="http://opengameart.org/content/fruit-and-veggie-inventory">created by Joshua Taylor</a> for the <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0. <p>La <a href="https://github.com/kypeli/kQOAuth">libreria kQOAuth</a> è proprietà di <a href="http://www.johanpaul.com/">Johan Paul</a> ed è licenziata come LGPL 2.1.</p><p>La <a href="https://github.com/vmg/sundown">libreria sundown Markdown </a> è proprietà di Natacha Port&eacute;, Vicent Marti e altri, e <a href="https://github.com/vmg/sundown#license">resa disponibile con licenza permissiva</a>.</p><p>Il logo di Pumpa è stato <a href="http://opengameart.org/content/fruit-and-veggie-inventory">creato da Joshua Taylor</a> per la <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.Il logo è proprietà dell'artista ed è doppiamente licenziato come CC-BY-SA 3.0 e GNU GPL 3.0. Copyright &copy; 2013-2015 Mats Sj&ouml;berg Copyright &copy; 2014 Mats Sj&ouml;berg {2013-2015 ?} Copyright &copy; 2014 Mats Sj&ouml;berg Copyright &copy; 2014 Mats Sj&ouml;berg Copyright &copy; 2013 Mats Sj&ouml;berg.</p> Copyright &copy; 2013 Mats Sj&ouml;berg.</p> You have %Ln new notification(s). Your &profile Close tab <p>Report bugs and feature requests at <a href="%5">%5</a>.</p> <p>Riporta problemi e richieste di funzionalità su <a href="%5">%5</a>.</p> A simple Qt-based pump.io client. Un semplice client pump.io basato su Qt. About %1 A proposito di %1 Site not configured yet! Sito non ancora configurato! Follow pump.io user Segui un utente pump.io Enter webfinger ID of person to follow: Inserisci il webfinger ID della persona che vuoi seguire: Sorry, that doesn't even look like a webfinger ID! Scusa, questo non sembra un webfinger ID! Sorry, you are already following that person! Scusa, stai già seguendo questa persona! Invalid user: Utente non valido: Invalid user (cannot check site): Utente non valido (non riesco a verificare il sito): &Context &Contesto &Followers Follo&wers F&ollowing F&ollowing F&avorites Pre&feriti A&ctivities Attiv&ità Loading ... Caricamento ... Ready! Pronto! Unable to post message! Non riesco a postare il messaggio! Unable to upload image! Impossibile caricare l'immagine! Network or authorisation error [%1/%2] %3. Errore di rete o di autorizzazione [%1/%2] %3. Successfully followed Following riuscito Successfully unfollowed Smesso di seguire correttamente PumpaSettingsDialog Account Account Not logged in currently. Attualmente disconnesso. Change account Cambia account Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time. Cliccando "Cambia account" partirà una nuova procedura di autenticazione per un nuovo account pump.io. Questo rimuovera le credenziali di accesso correnti, visto che attualmente Pumpa supporta solo un account per volta. Interface Interfaccia Update interval (in minutes): Intervallo di aggiornamento (in minuti): Public Pubblico Followers Followers Default "To": "A" predefiniti: Default "CC": "CC" predefiniti: Use icon in system tray Usa l'icona nell'area di notifica Show message character count Notifications Notifiche Never Mai Direct only Solo messaggi diretti Direct or mention Messaggi diretti e menzioni Direct, mention or inbox Messaggi diretti, menzioni e messaggi in arrivo Anything Tutto Highlight tray icon on: Evidenzia l'icona nella barra delle notifiche se: Popup notification on: Notifiche popup se: Currently logged in as %1. Attualmente loggato come %1. QASObject Deleted %1 Eliminato %1 QObject a few seconds ago pochi secondi fa one minute ago un minuto fa %n minutes ago %n minuti fa %n minuti fa one hour ago un'ora fa %n ore fa %n hours ago %n ore fa %n ore fa one day ago un giorno fa %n giorni fa %n days ago %n giorni fa %n giorni fa one week ago una settimana fa %n settimane fa %n weeks ago %n settimane fa %n settimane fa one month ago un mese fa %n mesi fa %n months ago %n mesi fa %n mesi fa one year ago un anno fa %n anni fa %n years ago %n anni fa %n anni fa %n minute(s) ago %n minuto fa %n minuti fa %n hour(s) ago %n ora fa %n ore fa pumpa-0.9.2/translations/pumpa_nvi.ts0000644000175000017500000014523612653206625016447 0ustar matsmats ActivityWidget via %1 ìlä %1 To: Ne: CC: Kop: Public Frapo ActorWidget stop following ke 'eylan si follow 'eylan si stop minimising posts Tse'a upxareti a'änsyem auto-minimise posts Tse'a upxare ahì'm Are you sure you want to stop following %1? Srake nga new ke 'eylan sivi hu %1? CollectionWidget Load older Run 'upxareti aham EditProfileDialog &Change picture &Latem relit Real name Hometown Bio Select Image Ftxey relit Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) ayRel (*.png *.jpg *.jpeg *.gif);; ayu (*.*) Sorry! Ngaytxoa! That file didn't appear to be an image. Tsa'u ke lu rel. FileDownloader Network error: Tìngäzìk apuslltxe: Network error: too many redirections! Unable to download %1 (Error #%2). Ke tsun mivunge %1it (Tìngäzìk #%2). Could not open file %1 for writing: Pamrelìri oel ke tsun Tsive'a %1it: FullObjectWidget delete 'aku edit comment säplltxevi comment Button to comment on a post säplltxevi [No description] [Kea tìsla'tsu] at %1 at ro %1 at %1 location of person ro %1 Profile last updated %1 updated %1 at %1 time when post was published ro %1 unlike ke sunu like sunu share käsrin stop following ke 'eylan si follow 'eylan si follow %1 'eylan si hu %1 You like this. Nga sunu fì'uru. and %Ln other person(s) sì %n tute sì %n sute shared this. Many persons shared kamäsrin fì'uti. shared this. One person shared kamäsrin fì'uti. %Ln person(s) shared this. %n tute kamäsrin fì'uti. %n sute kamäsrin fì'uti. Show all replies post Name of object type. 'upxare note Name of object type. 'upxare comment Name of object type. säplltxevi image Name of object type. rel likes this. marcó «me gusta». %1 like this. like this. fì'u sunu %1ur. %1 likes this. fì'u sunu %1ur. and 1 other person sì 'awa tuteti alahe and %1 other persons sì %1 suteti alahe shared this. kamäsrin fì'uti. 1 person shared this. 'awa tutel kamäsrin fì'uti. %1 persons shared this. %1a sute kamäsrin fì'uti. Show all %1 replies Wìntxu 'upxareti a%1 post 'upxare note 'upxare image rel Are you sure you want to delete this %1? Srake nga new 'ivaku %1it? Share this %1 by %2? käsrin fì%1 ìlä %2? Are you sure you want to stop following Srake nga ke new 'eylan sivi MessageEdit Spelling suggestions... MessageRecipients - - MessageWindow [markup] [markup] Public Frapo Followers Eylan To: Ne: Cc: Kop: &Remove picture &'aku relit + &To + &Ne + &Cc + &Kop Picture title (optional) Relyä tstxo (ke kamin) Use Markdown Sar markdownit [help] [srung] Title (optional) Tstxo (ke kamin) Cancel Ftang Preview Tse'a Send message Fpe' 'upxareti Ca&ncel &Preview &Send message Select recipient (To) Ftxey suteti (Ne) Select recipient (Cc) Ftxey suteti (Kop) Post a note Ngop 'upxareti Post a reply Ngop säplltxeviti &Send comment &Send post Edit object Edit post Edit comment Edit image post &Update object &Update post &Update comment &Update image post Select Image Ftxey relit Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) ayRel (*.png *.jpg *.jpeg *.gif);; ayu (*.*) Sorry! Ngaytxoa! That file didn't appear to be an image. Tsa'u ke lu rel. &Add picture &Sung relit &Change picture &Latem relit Characters: %1 OAuthFirstPage Welcome to Pumpa! Ziva'u nìprrte Pumparu! <p>In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>When you are done enter your new pump.io account id below in the form of <b>username@servername</b>.</p> <p>Nga kin ngivop accountit fte sar pumpit. Txo nga ngivop mi fwa frapori ngal tsun fmivi pumpit pumpro. <br />Rutxe kä <a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>ur.</p><p>Rutxe pamrel si ngeyä tstxo apump mìfa, sweylu txo livu <b>tstxo@tseng</b></p> <b>Your pump.io account id:</b> <b>Pump.io ngeyä account tstxo:</b> Use secure connection (recommended) Hawnu ngeyä tìpängkxoti pumpahu Next Hay OAuthSecondPage Authorise Pumpa Tung pumpati In order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy &amp; paste the <b>verifier</b> text string back into the field below. (Thetoken should be automatically pre-filled.) Nga kin tivung Pumpati fte sar pumpati ìlä webit. Pumpal wìntxu webit ngaru, rutxe kam si sänumeit, pamrel si <b>verìfer (verifier)</b> nema tsengäo In order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy &amp; paste the <b>verifier</b> text string back into the field below. (The token should be automatically pre-filled.) Ngal kin tivung pumpati fte pumpal tsun pivlltxe pumpur. Pumpa wayìntxu pageit ngaru. Nga pamrel si pumparu fwa <b>verifier</b>. Token: Tokìn: Verifier: Verifier: OAuthWizard Registering client ... Pawm futa pumpìl kìyevame fìpumpati ... Network error: Tìngäzìk apuslltxe: Registered client to [%1] successfully. Pumpìl kerame fìpumpati [%1]. Authorising user ... Steftxaw tuteti ... Network or authentication error! Puslltxe fu steftxaw tìngäzìk! ObjectWidget Re: 'Eyng: show context tse'a 'upxareti PumpApp &Inbox &Payfya &Mentions &Oeteri &Direct &Oeri Mean&while Lef&krr Copyright &copy; 2013 Mats Sj&ouml;berg Copyright &copy; 2013 Mats Sj&ouml;berg Fi&rehose &Paypìpe &Followers &Nga sunu Faysuteru F&ollowing &Eylan You have new messages. 'upxare amip lu ngaru. You have %Ln new notification(s). Error: Tìngäzìk: %1 &Window %1 &Wìntow Hide Wan Show Wìntxu E&xit &Hum Ctrl+Q Ctrl+Q Preferences Oeyä ayTìlatem &Reload timeline &Nìn payfya Ctrl+R Ctrl+R Load older in timeline Nìn 'upxare aham Ctrl+O Ctrl+O F&ollow an account &'eylan si Ctrl+L Ctrl+L Your &profile &About &Pumpateri About &Qt &Qtteri New &Note &Ngop 'upxareti Ctrl+N Ctrl+N Ctrl+D Followers Nga sunu Faysuteru SSL Error: Tìhawnu Tìngäzìk: %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. %1 ke tsun stiveftxaw pumpur. Kxawm kxutul tse'a tìpängkxoti ngayä hu pump fu tìngäzìk lu latemyuru. SSL Server certificate. Tìhawnu Tìngäzìk (SSLa certificate). Issued to: Telyu: Issued by: Ngopyu: Effective: Expires: MD5 digest: <b>Untrusted SSL connection!</b> <b>Oel ke omum hawnuyuti!</b> Public Frapo You have %1 new notifications. %1a 'upxare amip lu ngaru. %1 commented: %1 pamrel si: %1 wrote: %1 pamrel si: Report &bug online Close tab Ctrl+W Firehose Paypìpe Following Eylan Favorites Tsay'u sunu ngaru Activities &Pumpa &Pumpa &Tabs &Help &Srung <p>Pumpa 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 3 of the License, or (at your option) any later version.</p><p>Pumpa 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.</p><p>You should have received a copy of the GNU General Public License along with Pumpa. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> <p>The <a href="https://github.com/kypeli/kQOAuth">kQOAuth library</a> is copyrighted by <a href="http://www.johanpaul.com/">Johan Paul</a> and licensed under LGPL 2.1.</p><p>The <a href="https://github.com/vmg/sundown">sundown Markdown library</a> is copyrighted by Natacha Port&eacute;, Vicent Marti and others, and <a href="https://github.com/vmg/sundown#license">permissively licensed</a>.</p><p>The Pumpa logo was <a href="http://opengameart.org/content/fruit-and-veggie-inventory">created by Joshua Taylor</a> for the <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0. Copyright &copy; 2013-2015 Mats Sj&ouml;berg Copyright &copy; 2013 Mats Sj&ouml;berg {2014 ?} {2013-2015 ?} Copyright &copy; 2014 Mats Sj&ouml;berg Copyright &copy; 2013 Mats Sj&ouml;berg {2014 ?} Copyright &copy; 2013 Mats Sj&ouml;berg.</p> Copyright &copy; 3735 Mats Sj&ouml;berg.</p> <p>Report bugs and feature requests at <a href="%5">%5</a>.</p> A simple Qt-based pump.io client. Pumpìri pumpa fkeytok fte sar. About %1 Teri %1 Site not configured yet! Fìtseng ke lu alaksi mi! Follow pump.io user 'eylan si tuteru Enter webfinger ID of person to follow: Pamrel si tstxour apump fte 'eylan si: Sorry, that doesn't even look like a webfinger ID! Ngaytxoa, tsatstxo ke lu tstxo! Sorry, you are already following that person! Ngaytxoa, tsasute lu ngeyä 'eylan li! Invalid user: Tute lu eyawr: Invalid user (cannot check site): Tute ke lu eyawr (ke steftxaw pumpit): &Context &Upxare F&avorites &Tsay'u sunu ngaru A&ctivities Loading ... Nerìn ... Ready! Tam! Unable to post message! Ke tsun fpe' 'upxareti! Unable to upload image! Network or authorisation error [%1/%2] %3. Oel ke tsun pivängkxo hu pump fu pumpìl ke omum ngati [%1/%2] %3. Network or authorisation error [%1/%2]. puslltxe fu steftxaw tìngäzìk [%1/%2]. Successfully followed 'eylan soli Successfully unfollowed Ke 'eylan seri set PumpaSettingsDialog Account Account Not logged in currently. Ke fpxäkìm mi. Change account Latem accountit Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time. Clerìk san Latem accountit sik fte fpxäkìm accounthu amip. Fìaccount fìkem 'ayaku ngeyä sänumeti, Pumpa tsun sivar 'awa accountit. Interface Tseng fte tse'a Update interval (in minutes): Pehrr a nìn payfyati (mì aymìnut): Public Frapo Followers Nga sunu faysuteru Default recipient: Destinatario predeterminado: Default "To": Default "CC": Use icon in system tray Fìelturi lefngap trayä sar relit Show message character count Notifications ayu amip Never Kawkrr Direct only Oeri nì'aw Direct or mention Oeri fu oeteri Direct, mention or inbox Oeri, oeteri fu payfya Anything 'uo Highlight tray icon on: Atan si relur trayä mì: Popup notification on: Ngop wìntow mì: Currently logged in as %1. Nga fpxoläkìm na %1. QASObject Deleted %1 QObject a few seconds ago set one minute ago 'aw mìnut kam %n minutes ago one hour ago %n hours ago one day ago %n days ago one week ago %n weeks ago one month ago %n months ago one year ago %n years ago %n minute(s) ago %na mìnut kam %na aymìnut kam %n hour(s) ago %na hour kam %na ayhour kam %1 minutes ago %1a mìnut kam 1 hour ago 'awa hour kam %1 hours ago %1a ayhour kam pumpa-0.9.2/translations/pumpa_fi.qm0000644000175000017500000005345212653206625016236 0ustar matsmats}x>``')wlA02 @k$ HNHvVхvZ@7~Ae/~AeU!)!4?$^O& A #!I )aK{ +"e: 0p,# =I M13 lf1h ' : n 1 ľE ?B ?) .mE TG! TG UoM d<4 g eT, n   y - `LD Pu ȗG `9 oM) p3 NP @ 3d 66p8 7p8 F(A G+ I :N0 X XYq Zc- e4 *D =*E ΡK; N& ; O : F /P< H /y /y }00 d I J X iFC5 mC5> n< o%.T %6+ J1 |1"E M jI-.D<%8U8UKqM'r+,i>4J)<Mnt/eŔ)N>rNs|iP" sovelluksella %1 via %1ActivityWidgetCC:CC:ActivityWidgetJulkinenPublicActivityWidgetTo:To:ActivityWidgetTHaluatko lopettaa henkiln %1 seuraamisen?+Are you sure you want to stop following %1? ActorWidget@pienenn viestit automaattisestiauto-minimise posts ActorWidget seuraafollow ActorWidget$lopeta seuraaminenstop following ActorWidget:lopeta viestien pienentminenstop minimising posts ActorWidgetLataa vanhemmat Load olderCollectionWidgetVaihda &kuva&Change pictureEditProfileDialog KuvausBioEditProfileDialogKotipaikkasiHometownEditProfileDialogKuvatiedostot (*.png, *.jpg, *.jpeg, *.gif);;Kaikki tiedostot (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*)EditProfileDialog Nimesi Real nameEditProfileDialogValitse kuva Select ImageEditProfileDialog Hups!Sorry!EditProfileDialogNTm tiedosto ei nyttisi olevan kuva.'That file didn't appear to be an image.EditProfileDialogrTiedoston %1 avaaminen kirjoittamista varten ei onnistu: $Could not open file %1 for writing: FileDownloaderVerkkovirhe:Network error: FileDownloaderVVerkkovirhe: liian monta uudelleenohjausta!%Network error: too many redirections!FileDownloader^Tiedoston %1 lataaminen ei onnistu (virhe #%2)."Unable to download %1 (Error #%2).FileDownloader&%1 tykkvt tst. %1 like this.FullObjectWidget %1 tykk tst.%1 likes this.FullObjectWidget2yksi henkil jakoi tmn.8%Ln henkil jakoivat tmn.%Ln person(s) shared this.FullObjectWidget4Haluatko poistaa tmn %1?(Are you sure you want to delete this %1?FullObjectWidget@Profiilia pivitetty viimeksi %1Profile last updated %1FullObjectWidget<Haluatko jakaa henkiln %2 %1?Share this %1 by %2?FullObjectWidget.Nyt kaikki vastauksetShow all repliesFullObjectWidget&Sin tykkt tst.You like this.FullObjectWidget[Ei kuvausta][No description]FullObjectWidget&ja yksi muu henkil*ja %Ln muuta henkiland %Ln other person(s)FullObjectWidgetpaikassa %1at %1FullObjectWidget %1time when post was publishedat %1FullObjectWidget vastaacommentFullObjectWidgetkommentinName of object type.commentFullObjectWidget poistadeleteFullObjectWidgetmuokkaaeditFullObjectWidget seuraafollowFullObjectWidget$seuraa henkil %1 follow %1FullObjectWidget kuvanimageFullObjectWidget tykklikeFullObjectWidgetviestinnoteFullObjectWidget postinpostFullObjectWidgetjaashareFullObjectWidgetjakoivat tmn. shared this.FullObjectWidgetjakoi tmn.One person shared shared this.FullObjectWidget$lopeta seuraaminenstop followingFullObjectWidgetl tykkknunlikeFullObjectWidgetpivitetty %1 updated %1FullObjectWidget4Oikeinkirjoitusehdotukset &Spelling suggestions... MessageEdit--MessageRecipientsLis &kuva &Add picture MessageWindowVaihda &kuvaa&Change picture MessageWindow&Esikatselu&Preview MessageWindowPoista &kuva&Remove picture MessageWindow&Lhet vastaus &Send comment MessageWindow&Lhet viesti &Send message MessageWindow&Lhet viesti &Send post MessageWindow&&Pivit kommenttia&Update comment MessageWindow6&Pivit kuvallista viesti&Update image post MessageWindow &Pivit objekti&Update object MessageWindow &Pivit viesti &Update post MessageWindow + &Cc+ &Cc MessageWindow + &To+ &To MessageWindow &PeruCa&ncel MessageWindowKopiot (CC):Cc: MessageWindowMerkkej: %1Characters: %1 MessageWindow$Muokkaa kommenttia Edit comment MessageWindow$Muokaa kuvaviestiEdit image post MessageWindowMuokkaa oliota Edit object MessageWindowMuokkaa viesti Edit post MessageWindowKuvatiedostot (*.png, *.jpg, *.jpeg, *.gif);;Kaikki tiedostot (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) MessageWindowLhet viesti Post a note MessageWindowLhet vastaus Post a reply MessageWindowValitse kuva Select Image MessageWindow4Valitse vastaanottaja (CC)Select recipient (Cc) MessageWindow4Valitse vastaanottaja (To)Select recipient (To) MessageWindow Hups!Sorry! MessageWindowNTm tiedosto ei nyttisi olevan kuva.'That file didn't appear to be an image. MessageWindow.Otsikko (vapaaehtoinen)Title (optional) MessageWindow(Vastaanottajat (To):To: MessageWindow Kyt Markdownia Use Markdown MessageWindow [ohje][help] MessageWindow:<b>Pump.io-tilisi tunnus:</b>Your pump.io account id:OAuthFirstPage<p>Kyttksesi pump.io:ta sinun pit ensin rekisterid tunnus jollekin pump.io-palvelimelle. Jos et ole viel tehnyt sit, voit tehd nyt jollekin olemassa olevalla palvelimelle: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>Kun olet valmis, syt uuden pump.io-tilin tunnus alle muodossa <b>kyttj@palvelin</b>.</p>p

      In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers:
      http://pump.io/tryit.html.

      When you are done enter your new pump.io account id below in the form of username@servername.

      OAuthFirstPageSeuraavaNextOAuthFirstPageHKyt salattua yhteytt (suositeltu)#Use secure connection (recommended)OAuthFirstPage:Tervetuloa kyttmn Pumpaa!Welcome to Pumpa!OAuthFirstPageValtuuta PumpaAuthorise PumpaOAuthSecondPageTJotta Pumpa voisi lukea ja lhett uusia viestej pump.io-tilillesi, sinun tytyy mynt Pumpalle oikeus WWW-sivun kautta. Pumpa avaa sinulle oikean WWW-sivun. Seuraa sivun ohjeita ja kopioi sielt <b>verfier</b>-teksti alla olevaan kenttn. (Token-kentn pitisi olla automaattisesti tytetty.)HIn order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy & paste the verifier text string back into the field below. (The token should be automatically pre-filled.)OAuthSecondPage Token:Token:OAuthSecondPageVerifier: Verifier:OAuthSecondPage,Valtuutetaan kyttj &Authorising user ... OAuthWizardVerkkovirhe: Network error:  OAuthWizard@Verkko- tai tunnistautumisvirhe! Network or authentication error! OAuthWizardtAsiakasohjelma rekisterity onnistuneesti palvelimelle %1.'Registered client to [%1] successfully. OAuthWizard>Rekisteridn asiakasohjelmaa &Registering client ... OAuthWizardRe: Re:  ObjectWidget nyt keskustelu show context ObjectWidget\n%1 ei pysty varmistamaan palvelimen identiteetti. Tm virhe voi tarkoittaa sit, ett joku yritt tarjota vrennetty palvelinta tai sitten palvelimen yllpitj on tehty jonkin virheen. %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. PumpApp%1 &ikkuna %1 &WindowPumpApp%1 kommentoi: %1 commented: PumpApp%1 kirjoitti:  %1 wrote: PumpApp&Tietoja&AboutPumpApp&Keskustelu&ContextPumpAppV&astaanotetut&DirectPumpApp&Seuraajat &FollowersPumpApp &Ohje&HelpPumpApp&Viestit&InboxPumpApp&Maininnat &MentionsPumpApp &Pumpa&PumpaPumpApp2Lataa &aikajana uudelleen&Reload timelinePumpApp&Vlilehdet&TabsPumpApp@<b>Epluotettava SSL-yhteys!</b> Untrusted SSL connection!PumpApp<p>Tee vikailmoitukset ja ehdotukset uusista ominaisuuksista osoitteeseen <a href="%5">%5</a>.</p>?

      Report bugs and feature requests at %5.

      PumpAppfYksinkertainen Qt-pohjainen pump.io-asiakasohjelma.!A simple Qt-based pump.io client.PumpApp&Aktiviteetit A&ctivitiesPumpAppTietoja %1staAbout %1PumpAppTietoja &Qt:st About &QtPumpAppAktiviteetit ActivitiesPumpAppSulje vlilehti Close tabPumpApp Ctrl+DCtrl+DPumpApp Ctrl+LCtrl+LPumpApp Ctrl+NCtrl+NPumpApp Ctrl+OCtrl+OPumpApp Ctrl+QCtrl+QPumpApp Ctrl+RCtrl+RPumpApp Ctrl+WCtrl+WPumpApp&LopetaE&xitPumpApp&Voimassaolo alkaa:  Effective: PumpAppRSyt seurattavan henkiln webfinger-ID: (Enter webfinger ID of person to follow: PumpAppVirhe: Error: PumpApp*Voimassaolo pttyy:  Expires: PumpAppS&uosikit F&avoritesPumpApp&Seuraa tiliF&ollow an accountPumpAppS&euratut F&ollowingPumpAppSuosikit FavoritesPumpAppFi&rehose Fi&rehosePumpAppFirehoseFirehosePumpApp0Seuraa pump.io-kyttjFollow pump.io userPumpAppSeuraajat FollowersPumpAppSeuratut FollowingPumpAppPiilotaHidePumpApplToimimaton tunnus (sivustoa ei pystyta tarkistamaan): "Invalid user (cannot check site): PumpApp$Toimimaton tunnus:Invalid user: PumpAppMyntj:  Issued by: PumpAppKohde:  Issued to: PumpApp0Lataa vanhempia viestejLoad older in timelinePumpAppLadataan & Loading ...PumpAppMD5-tiiviste:  MD5 digest: PumpApp&Sill vlin Mean&whilePumpAppVVerkko- tai tunnistautumisvirhe [%1/%2] %3.*Network or authorisation error [%1/%2] %3.PumpAppUusi &viesti New &NotePumpAppAsetukset PreferencesPumpAppJulkinenPublicPumpAppValmis!Ready!PumpApp"Tee &vikailmoitusReport &bug onlinePumpAppSSL-virhe:  SSL Error: PumpApp4SSL-palvelinsertifikaatti.SSL Server certificate. PumpApp NytShowPumpAppBSivustoa ei ole viel mritelty!Site not configured yet!PumpApplAnteeksi, mutta se ei edes nyttnyt webfigner-ID:lt!2Sorry, that doesn't even look like a webfinger ID!PumpAppRAnteeksi, mutta seuraat jo tt henkil!-Sorry, you are already following that person!PumpApp(Seurataan kttj: Successfully followed PumpAppBKyttjn seuraaminen lopetettu: Successfully unfollowed PumpApp@Viestin lhettminen ei onnistu!Unable to post message!PumpApp8Kuvan lataaminen ei onnistu!Unable to upload image!PumpApp2Sinulle on uusi ilmoitus.@Sinulle on %Ln uutta ilmoitusta.!You have %Ln new notification(s).PumpApp&Profiilisi Your &profilePumpAppTiliAccountPumpaSettingsDialogKaikistaAnythingPumpaSettingsDialogVaihda tiliChange accountPumpaSettingsDialogpPainamalla "Vaihda tili" tehdn uudet tunnistautumisasetukset pump.io-tilille. Tm poistaa nykyisen kirjautumisen tunnistautumistiedot, koska Pumpa tukee vain yht tili kerrallaan.Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time.PumpaSettingsDialogDKirjauduttu sisn tunnuksella %1.Currently logged in as %1.PumpaSettingsDialog4Oletusvastaanottajat (CC): Default "CC":PumpaSettingsDialog4Oletusvastaanottajat (To): Default "To":PumpaSettingsDialog>Vain vastaanotetusta viesteist Direct onlyPumpaSettingsDialogVVastaanotetuista viesteist ja maininnoistaDirect or mentionPumpaSettingsDialogfVastaanotetuista, maininnoista ja muista viesteistDirect, mention or inboxPumpaSettingsDialogSeuraajat FollowersPumpaSettingsDialog<Korosta ilmoitusalueen kuvake:Highlight tray icon on:PumpaSettingsDialogKyttliittym InterfacePumpaSettingsDialogEi koskaanNeverPumpaSettingsDialog,Ei kirjauduttu sisn.Not logged in currently.PumpaSettingsDialogIlmoitukset NotificationsPumpaSettingsDialogNyt ilmoitus:Popup notification on:PumpaSettingsDialogJulkinenPublicPumpaSettingsDialog&Nyt merkkilaskuriShow message character countPumpaSettingsDialog2Pivitysvli (minuuteja):Update interval (in minutes):PumpaSettingsDialogTNyt kuvake jrjestelmn ilmoitusalueellaUse icon in system trayPumpaSettingsDialogPoistettu %1 Deleted %1 QASObjectpiv sitten %n piv sitten %n days agoQObjecttunti sitten %n tuntia sitten %n hours agoQObjectminuutti sitten&%n minuuttia sitten%n minutes agoQObjectkuukausi sitten&%n kuukautta sitten %n months agoQObjectviikko sitten"%n viikkoa sitten %n weeks agoQObjectvuosi sitten %n vuotta sitten %n years agoQObject,muutama sekunti sittena few seconds agoQObjectpiv sitten %n piv sitten one day agoQObjecttunti sitten %n tuntia sitten one hour agoQObjectminuutti sittenone minute agoQObjectkuukausi sitten&%n kuukautta sitten one month agoQObjectviikko sitten"%n viikkoa sitten one week agoQObjectvuosi sitten %n vuotta sitten one year agoQObjectpumpa-0.9.2/translations/pumpa_fr.qm0000644000175000017500000004162712653206625016250 0ustar matsmats] ,+,Y,,,0k~{d{2{=~I.{2M q\  6 aY R.B &b< qE,El+{3g3g;S-@x4``Wl50 =2k$LHN<Z@/~Ae~Ae ^?Z 6D +"e1 lf+ 4 n 1 ľ: 5` .m9 TG TG eT% R [ Pu % ȗ< F(6 G I :N+3 ^ Zc% e- N 1 : < /y /y }0 n2 o% %. = |1 > j=?.D %8U0qMMnq7!Ŕ`4i? via %1 via %1ActivityWidgetCC:CC:ActivityWidget PublicPublicActivityWidget:To:ActivityWidget suivrefollow ActorWidget"arrter de suivrestop following ActorWidgetPlus anciens Load olderCollectionWidget &Changer d'image&Change pictureEditProfileDialogFichers images (*.png *.jpg *.jpeg *.gif);;Tous les fichiers (*.*) 7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*)EditProfileDialog"Choisir une image Select ImageEditProfileDialogDsol!Sorry!EditProfileDialogPCe fichier ne semble pas tre une image.'That file didn't appear to be an image.EditProfileDialog^chec de l'ouverture du fichier %1 en criture:$Could not open file %1 for writing: FileDownloader(Erreur de connexion:Network error: FileDownloaderRIncapable de tlcharger %1 (Erreur #%2)."Unable to download %1 (Error #%2).FileDownloader%1 aime. %1 like this.FullObjectWidget%1 aime.%1 likes this.FullObjectWidgetTtes vous sr de vouloir supprimer ce %1 ?(Are you sure you want to delete this %1?FullObjectWidget([Pas de dscription][No description]FullObjectWidget %1location of personat %1FullObjectWidget %1time when post was publishedat %1FullObjectWidgetcommenterButton to comment on a postcommentFullObjectWidgetcommenterName of object type.commentFullObjectWidgetsupprimerdeleteFullObjectWidget suivrefollowFullObjectWidget aimerlikeFullObjectWidgetpartagershareFullObjectWidgeta partag ceci.Many persons shared shared this.FullObjectWidgeta partag ceci.One person shared shared this.FullObjectWidget"arrter de suivrestop followingFullObjectWidgetne plus aimerunlikeFullObjectWidget$&Ajouter une image &Add picture MessageWindow &Changer d'image&Change picture MessageWindow$&Supprimer l'image&Remove picture MessageWindowCc:Cc: MessageWindowFichers images (*.png *.jpg *.jpeg *.gif);;Tous les fichiers (*.*) 7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) MessageWindowcrire une note Post a note MessageWindow$crire une rponse Post a reply MessageWindow"Choisir une image Select Image MessageWindowDsol!Sorry! MessageWindowPCe fichier ne semble pas tre une image.'That file didn't appear to be an image. MessageWindow:To: MessageWindow<<b>Votre identit pump.io:</b>Your pump.io account id:OAuthFirstPage<p>Pour utiliser pump.io, vous devez d'abord crer un compte sur un serveur pump.io. Si vous n'avez pas encore fait cela, faites le maintenant en essayant un des serveurs public existant: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>Lorsque vous avez termin, entrez votre nouveau compte pump.io sous le format <b>utilisateur@nom-du-serveur</b>.</p>p

      In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers:
      http://pump.io/tryit.html.

      When you are done enter your new pump.io account id below in the form of username@servername.

      OAuthFirstPageSuivantNextOAuthFirstPage(Bienvenue sur Pumpa!Welcome to Pumpa!OAuthFirstPageAutoriser PumpaAuthorise PumpaOAuthSecondPage Token:Token:OAuthSecondPageVerifier: Verifier:OAuthSecondPageRAutorisation de l'utilisateur en cours...Authorising user ... OAuthWizardErreur rseau:Network error:  OAuthWizardRErreur de connexion ou d'autentification! Network or authentication error! OAuthWizardNLe client a bien t enregistr [%1].'Registered client to [%1] successfully. OAuthWizard6Enregistrement du client...Registering client ... OAuthWizardRe:Re:  ObjectWidget(afficher le contexte show context ObjectWidget%1 &Fentre %1 &WindowPumpApp&A propos&AboutPumpApp&Contexte&ContextPumpApp&Directs&DirectPumpAppA&bonns &FollowersPumpApp &Aide&HelpPumpAppRcept&ion&InboxPumpApp&Mentions &MentionsPumpApp &Pumpa&PumpaPumpApp$&Recharger le flux&Reload timelinePumpApp<p>Pumpa 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 3 of the License, or (at your option) any later version.</p><p>Pumpa 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.</p><p>You should have received a copy of the GNU General Public License along with Pumpa. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p>

      Pumpa 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 3 of the License, or (at your option) any later version.

      Pumpa 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 Pumpa. If not, see http://www.gnu.org/licenses/.

      PumpApp<p>Rapports de bugs et demande de nouvelles fonctionnalits <a href="%5">%5</a>.</p>?

      Report bugs and feature requests at %5.

      PumpApp<p>La librairie <a href="https://github.com/kypeli/kQOAuth">kQOAuth library</a> est la proprit intelectuelle de <a href="http://www.johanpaul.com/">Johan Paul</a> et publi sous licence LGPL 2.1.</p><p>La <a href="https://github.com/vmg/sundown">librairie sundown Markdown</a> est la proprit de Natacha Port&eacute;, Vicent Marti et autres, et publi sous <a href="https://github.com/vmg/sundown#license">license permissive</a>.</p><p>Le logo de Pumpa a t cr par <a href="http://opengameart.org/content/fruit-and-veggie-inventory">Joshua Taylor</a> pour la <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.Le logo est la proprit intellectuelle de l'artiste et publi sous la licence double CC-BY-SA 3.0 et GNU GPL 3.0.

      The kQOAuth library is copyrighted by Johan Paul and licensed under LGPL 2.1.

      The sundown Markdown library is copyrighted by Natacha Porté, Vicent Marti and others, and permissively licensed.

      The Pumpa logo was created by Joshua Taylor for the Liberated Pixel Cup.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0.PumpApp>Un simple client pump.io en Qt.!A simple Qt-based pump.io client.PumpApp propos %1About %1PumpApp propos de &Qt About &QtPumpApp Ctrl+LCtrl+LPumpApp Ctrl+NCtrl+NPumpApp Ctrl+OCtrl+OPumpApp Ctrl+QCtrl+QPumpApp Ctrl+RCtrl+RPumpApp&FermerE&xitPumpAppREntez l'adresse de la personne a suivre: (Enter webfinger ID of person to follow: PumpAppErreur:Error: PumpApp"&Suivre un compteF&ollow an accountPumpAppAb&onnements F&ollowingPumpApp&Firehose Fi&rehosePumpApp@Suivre un utilisateur de pump.ioFollow pump.io userPumpAppAbonns FollowersPumpAppMasquerHidePumpAppjUtilisateur invalide (ne peux pas vrifier le site): "Invalid user (cannot check site): PumpApp*Utilisateur invalide:Invalid user: PumpApp\Charger des lments plus anciens dans le fluxLoad older in timelinePumpAppChargement... Loading ...PumpApp&Activits Mean&whilePumpAppNouvelle &Note New &NotePumpAppPrfrences PreferencesPumpApp PublicPublicPumpApp Prt!Ready!PumpAppAfficherShowPumpApp4Site pas encore configur!Site not configured yet!PumpAppDsol, cela ne ressemble mme pas une adresse au format webfinger!2Sorry, that doesn't even look like a webfinger ID!PumpAppPDsol, vous suivez dj cette personne!-Sorry, you are already following that person!PumpApp8Demande d'abonnement russieSuccessfully followed PumpApp@Demande de dsabonnement russieSuccessfully unfollowed PumpApp>Incapable d'envoyer un message!Unable to post message!PumpApp CompteAccountPumpaSettingsDialogToutAnythingPumpaSettingsDialog"Changer de compteChange accountPumpaSettingsDialogCliquer sur "Changer de compte" va redmarrer le processus d'autentification pour le nouveau compte pump.io. Ceci va supprimer les informations de connexion actuelles comme Pumpa ne supporte pas le multi-compte.Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time.PumpaSettingsDialog>Actuellement connect comme %1.Currently logged in as %1.PumpaSettingsDialogFSeulement pour les messages directs Direct onlyPumpaSettingsDialog8Messages directs ou mentionsDirect or mentionPumpaSettingsDialogPMessages directs, mentions ou rception Direct, mention or inboxPumpaSettingsDialogAbonns FollowersPumpaSettingsDialogfNotification dans l'icone de la bare de tche pour:Highlight tray icon on:PumpaSettingsDialogInterface InterfacePumpaSettingsDialog JamaisNeverPumpaSettingsDialog0Actuellement dconnect.Not logged in currently.PumpaSettingsDialogNotifications NotificationsPumpaSettingsDialogBNotification dans une popup pour:Popup notification on:PumpaSettingsDialog PublicPublicPumpaSettingsDialogJInterval de mise jour (en minutes):Update interval (in minutes):PumpaSettingsDialog6Icone dans la bare de tcheUse icon in system trayPumpaSettingsDialog0il y a quelques secondesa few seconds agoQObject"il y a une minuteone minute agoQObjectpumpa-0.9.2/translations/pumpa_es.qm0000644000175000017500000006436012653206625016247 0ustar matsmatsxP``(%wlSc0G2 S&k$ HNZ6VхZ@I~Ae~Ae!)!Q$~^`' S #"' #n )a] +"eL 0p, =I4 M1 lfB ( LT n' 1 { ľX Qv ?*k .mW TG TG Uo_~ eT- n y  - `^P PuN ȗY `K o_ pE `, Rj 3d* 66pJ 7pJ F(TF C G, I :NB ^03 XN XY Zc. eF *V =*WU Ρ][ N' N& a M) Xd /a Z /y /y }0 d4 I X iFCG mCG2 nN o%/$ %H/ \A |1# _ jZ.D%8UKBJU]bqM(p+-+>4J)NaMnq7 8uŔ*PNsfibO va %1 via %1ActivityWidgetCC:CC:ActivityWidgetPblicoPublicActivityWidget Para:To:ActivityWidgetPSeguro que deseas dejar de seguir a %1?+Are you sure you want to stop following %1? ActorWidget*auto-minimizar envosauto-minimise posts ActorWidget seguirfollow ActorWidgetdejar de seguirstop following ActorWidget2dejar de minimizar envosstop minimising posts ActorWidgetCargar antiguos Load olderCollectionWidget&Cambiar imagen&Change pictureEditProfileDialogBioBioEditProfileDialog CiudadHometownEditProfileDialogArchivos de imagen (*.png *.jpg *.jpeg *.gif);;Todos los archivos (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*)EditProfileDialogNombre real Real nameEditProfileDialog$Seleccionar imagen Select ImageEditProfileDialogLo siento!Sorry!EditProfileDialogBEse archivo no parece una imagen.'That file didn't appear to be an image.EditProfileDialog^No se pudo abrir el fichero %1 para escritura: $Could not open file %1 for writing: FileDownloaderError de red:Network error: FileDownloaderPError de red: demasiadas redirecciones!%Network error: too many redirections!FileDownloaderHNo se pudo descargar %1 (Error #%2)."Unable to download %1 (Error #%2).FileDownloaderA %1 les gusta. %1 like this.FullObjectWidget&A %1 le gusta esto.%1 likes this.FullObjectWidget4%n persona comparti esto.<%n personas compartieron esto.%Ln person(s) shared this.FullObjectWidgetNEsts seguro de que quieres borrar %1?(Are you sure you want to delete this %1?FullObjectWidgetFltima actualizacin del perfil: %1Profile last updated %1FullObjectWidget(Compartir %1 de %2?Share this %1 by %2?FullObjectWidget8Mostrar todas las respuestasShow all repliesFullObjectWidgetTe gusta esto.You like this.FullObjectWidget"[Sin descripcin][No description]FullObjectWidget y %n persona ms"y %n personas msand %Ln other person(s)FullObjectWidget en %1location of personat %1FullObjectWidget el %1time when post was publishedat %1FullObjectWidgetcomentarButton to comment on a postcommentFullObjectWidgetel comentarioName of object type.commentFullObjectWidget borrardeleteFullObjectWidget editareditFullObjectWidgetseguir afollowFullObjectWidgetseguir a %1 follow %1FullObjectWidgetla imagenName of object type.imageFullObjectWidgetme gustalikeFullObjectWidgetla notaName of object type.noteFullObjectWidgetla publicacinName of object type.postFullObjectWidgetcompartirshareFullObjectWidget$compartieron esto.Many persons shared shared this.FullObjectWidgetcomparti esto.One person shared shared this.FullObjectWidget"dejar de seguir astop followingFullObjectWidgetno me gustaunlikeFullObjectWidgetactualizado: %1 updated %1FullObjectWidget8Sugerencias de ortografa...Spelling suggestions... MessageEdit--MessageRecipients&Agregar imagen &Add picture MessageWindow&Cambiar imagen&Change picture MessageWindow&Previsualizar&Preview MessageWindowBorra&r imagen&Remove picture MessageWindow$&Enviar comentario &Send comment MessageWindow&Enviar mensaje &Send message MessageWindow&Enviar &Send post MessageWindow,&Actualizar comentario&Update comment MessageWindow$&Actualizar imagen&Update image post MessageWindow$&Actualizar objeto&Update object MessageWindow"&Actualizar envo &Update post MessageWindow + &Cc+ &Cc MessageWindow+ &Para+ &To MessageWindowCa&ncelarCa&ncel MessageWindowCc:Cc: MessageWindowCaracteres %1Characters: %1 MessageWindow"Editar comentario Edit comment MessageWindowEditar imagenEdit image post MessageWindowEditar objeto Edit object MessageWindow Editar Edit post MessageWindowArchivos de imagen (*.png *.jpg *.jpeg *.gif);;Todos los archivos (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) MessageWindow"Publicar una nota Post a note MessageWindow,Publicar una respuesta Post a reply MessageWindow$Seleccionar imagen Select Image MessageWindow8Selecciona destinatario (Cc)Select recipient (Cc) MessageWindow<Selecciona destinatario (Para)Select recipient (To) MessageWindowLo siento!Sorry! MessageWindowBEse archivo no parece una imagen.'That file didn't appear to be an image. MessageWindow"Ttulo (opcional)Title (optional) MessageWindow Para:To: MessageWindowUsar Markdown Use Markdown MessageWindow[ayuda][help] MessageWindowZ<b>Tu identificador de cuenta en pump.io:</b>Your pump.io account id:OAuthFirstPage4<p>Para poder usar pump.io, primero necesitas registrar una cuenta en un servidor pump.io. Si an no lo has hecho, puedes hacerlo ahora en uno de los servidores pblicos existentes: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>Cuando la tengas, introduce tu identificador de cuenta de pump.io en el formulario de abajo, en la forma <b>nombredeusuario@nombredeservidor</b>.</p>p

      In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers:
      http://pump.io/tryit.html.

      When you are done enter your new pump.io account id below in the form of username@servername.

      OAuthFirstPageSiguienteNextOAuthFirstPageDUsar conexin segura (recomendado)#Use secure connection (recommended)OAuthFirstPage(Bienvenido a Pumpa!Welcome to Pumpa!OAuthFirstPageAutorizar PumpaAuthorise PumpaOAuthSecondPagePara que Pumpa pueda leer y enviar mensajes en tu cuenta de pump.io, necesitas proporcionar acceso a Pumpa a travs del sitio web. Pumpa abrir la pgina web para t: simplemente sigue las instrucciones y copia y pega el texto <b>verificador</b> en el campo de ms abajo. (El 'token' debera rellenarse automticamente.)HIn order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy & paste the verifier text string back into the field below. (The token should be automatically pre-filled.)OAuthSecondPage Token:Token:OAuthSecondPageVerificador: Verifier:OAuthSecondPage,Autorizando usuario...Authorising user ... OAuthWizardError de red: Network error:  OAuthWizardBError de red o de autenticacin! Network or authentication error! OAuthWizardRCliente registrado en [%1] correctamente.'Registered client to [%1] successfully. OAuthWizard2Registrando el cliente...Registering client ... OAuthWizardRe:Re:  ObjectWidget mostrar contexto show context ObjectWidgett%1 no puede verificar la identidad del servidor. Este error puede significar que alguien est tratando de suplantar al servidor, o que el administrador del servidor ha cometido un error. %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. PumpApp%1 &Ventana %1 &WindowPumpApp%1 coment: %1 commented: PumpApp%1 escribi:  %1 wrote: PumpApp&Acerca de&AboutPumpApp&Contexto&ContextPumpApp&Directos&DirectPumpApp&Seguidores &FollowersPumpApp &Ayuda&HelpPumpApp&Bandeja de &entrada&InboxPumpApp&Menciones &MentionsPumpApp &Pumpa&PumpaPumpApp0&Recargar lnea temporal&Reload timelinePumpAppPes&taas&TabsPumpAppD<b>Conexin SSL no confiable!</b> Untrusted SSL connection!PumpApp<p>Pumpa es software libre: puedes distribuirlo y/o modificarlo bajo los trminos de la licencia GNU General Public License tal y como est publicada por la Free Software Foundation, ya sea en la versin 3 de la Licencia, o, si optas por ello, cualquier otra versin posterior.</p><p>Pumpa se distribuye con la esperanza de que resulte til, pero SIN GARANTA ALGUNA, ni siquiera la garanta que implica su COMERCIABILIDAD o ADECUIDAD PARA UN PROPSITO PARTICULAR. Vea la licencia GNU General Public License para ms detalles.</p><p>Debera haber recibido una copia de la licencia GNU General Public License junto con Pumpa. Si no es as, vea <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p>

      Pumpa 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 3 of the License, or (at your option) any later version.

      Pumpa 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 Pumpa. If not, see http://www.gnu.org/licenses/.

      PumpApp<p>Informes de error y solicitudes de caractersticas en <a href="%5">%5</a>.</p>?

      Report bugs and feature requests at %5.

      PumpApp<p>La <a href="https://github.com/kypeli/kQOAuth">biblioteca kQOAuth</a> est bajo derechos de autor de <a href="http://www.johanpaul.com/">Johan Paul</a> y licenciada bajo LGPL 2.1.</p><p>La <a href="https://github.com/vmg/sundown">biblioteca de Markdown sundown</a> est bajo derechos de autor de Natacha Port&eacute;, Vicent Marti y otros, y tiene <a href="https://github.com/vmg/sundown#license">licencia permisiva</a>.</p><p>El logo de Pumpa fue <a href="http://opengameart.org/content/fruit-and-veggie-inventory">creado por Joshua Taylor</a> para <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>. El logo tiene derechos de autor del artista y est licenciado dualmente bajo la licencia CC-BY-SA 3.0 y la licencia GNU GPL 3.0.

      The kQOAuth library is copyrighted by Johan Paul and licensed under LGPL 2.1.

      The sundown Markdown library is copyrighted by Natacha Porté, Vicent Marti and others, and permissively licensed.

      The Pumpa logo was created by Joshua Taylor for the Liberated Pixel Cup.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0.PumpAppRUn cliente pump.io sencillo basado en Qt.!A simple Qt-based pump.io client.PumpAppA&ctividades A&ctivitiesPumpAppAcerca de %1About %1PumpAppAcerca de &Qt About &QtPumpAppActividades ActivitiesPumpAppCerrar pestaa Close tabPumpAppXCopyright &copy; 2013-2015 Mats Sj&ouml;berg,Copyright © 2013-2015 Mats SjöbergPumpApp Ctrl+DCtrl+DPumpApp Ctrl+LCtrl+LPumpApp Ctrl+NCtrl+NPumpApp Ctrl+OCtrl+OPumpApp Ctrl+QCtrl+QPumpApp Ctrl+RCtrl+RPumpApp Ctrl+WCtrl+WPumpApp &SalirE&xitPumpAppValidez: Effective: PumpAppbIntroduce el webfinger ID de la persona a seguir:(Enter webfinger ID of person to follow: PumpApp Error:Error: PumpAppCaduca el: Expires: PumpAppF&avoritos F&avoritesPumpApp$Segu&ir una cuentaF&ollow an accountPumpAppS&iguiendo F&ollowingPumpAppFavoritos FavoritesPumpAppMangue&ra Fi&rehosePumpAppMangueraFirehosePumpApp6Seguir a un usuario pump.ioFollow pump.io userPumpAppSeguidores FollowersPumpAppSiguiendo FollowingPumpAppOcultarHidePumpAppfUsuario no vlido (no se puede comprobar el sitio):"Invalid user (cannot check site): PumpApp$Usuario no vlido:Invalid user: PumpAppEmitido por: Issued by: PumpAppEmitido para: Issued to: PumpAppBCargar antiguos en lnea temporalLoad older in timelinePumpAppCargando ... Loading ...PumpApp&Huella digital MD5: MD5 digest: PumpAppMientras &tanto Mean&whilePumpAppTError de red o de autorizacin [%1/%2] %3.*Network or authorisation error [%1/%2] %3.PumpAppNueva &Nota New &NotePumpAppPreferencias PreferencesPumpAppPblicoPublicPumpAppListo!Ready!PumpApp0Informar so&bre un falloReport &bug onlinePumpAppError SSL: SSL Error: PumpApp:Certificado SSL del servidor.SSL Server certificate. PumpAppMostrarShowPumpApp4Sitio an no configurado!Site not configured yet!PumpAppTLo siento, eso no parece un webfinger ID!2Sorry, that doesn't even look like a webfinger ID!PumpAppHLo siento, ya sigues a esa persona!-Sorry, you are already following that person!PumpApp4Siguiendo correctamente a Successfully followed PumpAppDDejaste correctamente de seguir a Successfully unfollowed PumpAppJNo se ha podido publicar el mensaje!Unable to post message!PumpAppDNo se ha podido cargar la imagen!Unable to upload image!PumpApp:Tienes %n nueva notificacin.@Tienes %n nuevas notificaciones.!You have %Ln new notification(s).PumpAppTu &perfil Your &profilePumpApp CuentaAccountPumpaSettingsDialog&Cualquier actividadAnythingPumpaSettingsDialogCambiar cuentaChange accountPumpaSettingsDialogPHacer clic en "Cambiar cuenta" lanzar la autenticacin para una nueva cuenta de pump.io. Esto eliminar las credenciales actuales ya que Pumpa slo permite una cuenta.Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time.PumpaSettingsDialog<Se ha iniciado sesin como %1.Currently logged in as %1.PumpaSettingsDialog("CC" predeterminado: Default "CC":PumpaSettingsDialog,"Para" predeterminado: Default "To":PumpaSettingsDialogSlo directos Direct onlyPumpaSettingsDialog(Directos o mencionesDirect or mentionPumpaSettingsDialogPDirectos, menciones o bandeja de entradaDirect, mention or inboxPumpaSettingsDialogSeguidores FollowersPumpaSettingsDialogFResaltar el icono de la bandeja en:Highlight tray icon on:PumpaSettingsDialogInterfaz InterfacePumpaSettingsDialog NuncaNeverPumpaSettingsDialog2No se ha iniciado sesin.Not logged in currently.PumpaSettingsDialogNotificaciones NotificationsPumpaSettingsDialog4Notificacin emergente en:Popup notification on:PumpaSettingsDialogPblicoPublicPumpaSettingsDialogVMostrar la cuenta de caracteres del mensajeShow message character countPumpaSettingsDialogPIntervalo de actualizacin (en minutos):Update interval (in minutes):PumpaSettingsDialogFUsar icono en la bandeja de sistemaUse icon in system trayPumpaSettingsDialogBorrado %1 Deleted %1 QASObjecthace %n dahace %n das %n days agoQObjecthace %n horahace %n horas %n hours agoQObjecthace %n minutohace %n minutos%n minutes agoQObjecthace %n meshace %n meses %n months agoQObjecthace %n semanahace %n semanas %n weeks agoQObjecthace %n aoshace %n aos %n years agoQObject$hace unos segundosa few seconds agoQObjecthace un dahace %n das one day agoQObjecthace una horahace %n horas one hour agoQObjecthace un minutoone minute agoQObjecthace un meshace %n meses one month agoQObjecthace una semanahace %n semanas one week agoQObjecthace un aohace %n aos one year agoQObjectpumpa-0.9.2/translations/pumpa_ru.qm0000644000175000017500000005501412653206625016262 0ustar matsmats\.7Ih/Ca/q i#o2S'64q.686H6'8W6ROai$*0muu#~&9QEV#L\]eedg:3334"4P4~40k~ {n{T  BaYR7]<.b<q0B3U,#l2{ 3g3g!NE<05r_ ;S5Fr>x@``)wlBs0M2 B2k$ 7HNIVхZ@8~Ae~Ae!)!@$^Q(t B #" )aM +"e; 0p- =I M1# lf2 ) ;g n 1 ľG @c ?+W .mG TG TG UoO d<4 eT.q n   . `N Pu\ ȗIJ `: oO P| AC 3d 66p9 7p9 F(CP G-q I :N2 X XYw Zc/w e5 *Fd =*F ΡL N( =/ Rb <@ G /R JB /y /y }0H d I K X iFC7 mC65 n= o%/ %7D K |1# P# jJ.D%8U:>UMqM)d+->4NJ)=lMnD0Ŕ+?NsXiS G5@57 %1 via %1ActivityWidget >?8O:CC:ActivityWidgetC1;8G=>PublicActivityWidget ><C:To:ActivityWidget6K E>B8B5 >B?8A0BLAO >B %1?+Are you sure you want to stop following %1? ActorWidgetF2B><0B8G5A:8 A2>@0G820BL A>>1I5=8Oauto-minimise posts ActorWidget>4?8A0BLAOfollow ActorWidgetB?8A0BLAOstop following ActorWidget05 A2>@0G820BL A>>1I5=8Ostop minimising posts ActorWidget<03@C78BL ?@54K4CI85 A>>1I5=8O Load olderCollectionWidget*&7<5=8BL 87>1@065=85&Change pictureEditProfileDialog8>3@0D8OBioEditProfileDialog >4=>9 3>@>4HometownEditProfileDialogz$09;K 87>1@065=89 (*.png *.jpg *.jpeg *.gif);;A5 D09;K (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*)EditProfileDialog0AB>OI55 <O Real nameEditProfileDialog&K1@0BL 7>1@065=85 Select ImageEditProfileDialog@>AB8B5!Sorry!EditProfileDialogF-B>B D09; =5 O2;O5BAO 87>1@065=85<.'That file didn't appear to be an image.EditProfileDialogH5 <>3C >B:@KBL D09; %1 4;O 70?8A8: $Could not open file %1 for writing: FileDownloaderH81:0 A5B8: Network error: FileDownloaderVH81:0 A5B8: A;8H:>< <=>3> ?5@5=0?@02;5=89!%Network error: too many redirections!FileDownloader>5 <>3C A:0G0BL %1 (H81:0 #%2)"Unable to download %1 (Error #%2).FileDownloader%1 MB> =@028BAO %1 like this.FullObjectWidget %1 MB> =@028BAO.%1 likes this.FullObjectWidget4%n G5;>25: ?5@5A:070; MB>.8%n G5;>25:0 ?5@5A:070;8 MB>.6%n G5;>25: ?5@5A:070;8 MB>.%Ln person(s) shared this.FullObjectWidgetJ2K C25@5=K, GB> E>B8B5 C40;8BL MB> %1(Are you sure you want to delete this %1?FullObjectWidget.@>D8;L 1K; >1=>2;Q= %1Profile last updated %1FullObjectWidget4>45;8BLAO MB8< %1< >B %2?Share this %1 by %2?FullObjectWidget0>:070BL 2A5 :><<5=B0@88Show all repliesFullObjectWidget"0< MB> =@028BAO.You like this.FullObjectWidget[5B >?8A0=8O][No description]FullObjectWidget&8 %n 4@C3>9 G5;>25:(8 %n 4@C38E G5;>25:0&8 %n 4@C38E G5;>25:and %Ln other person(s)FullObjectWidget@ %1at %1FullObjectWidget@ %1time when post was publishedat %1FullObjectWidget:><<5=B8@>20BLcommentFullObjectWidget:><<5=B0@89Name of object type.commentFullObjectWidgetC40;8BLdeleteFullObjectWidget87<5=8BLeditFullObjectWidget?>4?8A0BLAOfollowFullObjectWidget"?>4?8A0BLAO =0 %1 follow %1FullObjectWidget87>1@065=85imageFullObjectWidget=@028BAOlikeFullObjectWidgetA>>1I5=85noteFullObjectWidgetA>>1I5=85postFullObjectWidget?5@5A:070BLshareFullObjectWidget ?5@5A:070;8 MB>. shared this.FullObjectWidget?5@5A:070; MB>.One person shared shared this.FullObjectWidget>B?8A0BLAOstop followingFullObjectWidget=5 =@028BAOunlikeFullObjectWidget>1=>2;5=> %1 updated %1FullObjectWidget00@80=BK ?@02>?8A0=8O...Spelling suggestions... MessageEdit--MessageRecipients*&>1028BL 87>1@065=85 &Add picture MessageWindow*&7<5=8BL 87>1@065=85&Change picture MessageWindow@5&4?@>A<>B@&Preview MessageWindow(&#40;8BL 87>1@065=85&Remove picture MessageWindow,&B?@028BL :><<5=B0@89 &Send comment MessageWindow(&B?@028BL A>>1I5=85 &Send message MessageWindow(&B?@028BL A>>1I5=85 &Send post MessageWindow*&1=>28BL :><<5=B0@89&Update comment MessageWindowD&1=>28BL A>>1I5=85 A 87>1@065=85<&Update image post MessageWindow &1=>28BL >1J5:B&Update object MessageWindow&&1=>28BL A>>1I5=85 &Update post MessageWindow+ >&?8O+ &Cc MessageWindow+ &><C+ &To MessageWindowB&<5=0Ca&ncel MessageWindow >?8O:Cc: MessageWindow!8<2>;>2: %1Characters: %1 MessageWindow(7<5=8BL :><<5=B0@89 Edit comment MessageWindowB7<5=8BL A>>1I5=85 A 87>1@065=85<Edit image post MessageWindow7<5=8BL >1J5:B Edit object MessageWindow$7<5=8BL A>>1I5=85 Edit post MessageWindowz$09;K 87>1@065=89 (*.png *.jpg *.jpeg *.gif);;A5 D09;K (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) MessageWindow&B?@028BL A>>1I5=85 Post a note MessageWindowB?@028BL >B25B Post a reply MessageWindow&K1@0BL 7>1@065=85 Select Image MessageWindow4K1@0BL ?>;CG0B5;O (>?8O)Select recipient (Cc) MessageWindow2K1@0BL ?>;CG0B5;O (><C)Select recipient (To) MessageWindow@>AB8B5!Sorry! MessageWindowF-B>B D09; =5 O2;O5BAO 87>1@065=85<.'That file didn't appear to be an image. MessageWindow403>;>2>: (=5 >1O70B5;L=>)Title (optional) MessageWindow ><C:To: MessageWindow*A?>;L7>20BL Markdown Use Markdown MessageWindow[?><>IL][help] MessageWindowT<b>0H 845=B8D8:0B>@ pump.io 0::0C=B0:</b>Your pump.io account id:OAuthFirstPage2<p>;O B>3> GB>1K 8A?>;L7>20BL pump.io 2K 4>;6=K A=0G0;0 70@538AB@8@>20BL 0::0C=B =0 pump.io A5@25@5. A;8 2K 5IQ =5 A45;0;8 MB>3>, B> 2K <>65B5 A45;0BL MB> A59G0A 2>A?>;L7>202H8AL >4=8< 87 ACI5AB2CNI8E ?C1;8G=KE A5@25@>2: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>>340 2K 70:>=G8B5 22548B5 20H =>2K9 845=B5D8:0B>@ pump.io 0::0C=B0 2 D>@<5 <b>username@servername</b>.</p>p

      In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers:
      http://pump.io/tryit.html.

      When you are done enter your new pump.io account id below in the form of username@servername.

      OAuthFirstPage 0;55NextOAuthFirstPagedA?>;L7>20BL 157>?0A=>5 A>548=5=85 (@5:><5=4C5BAO)#Use secure connection (recommended)OAuthFirstPage2>1@> ?>60;>20BL 2 Pumpa!Welcome to Pumpa!OAuthFirstPage"2B>@870F8O PumpaAuthorise PumpaOAuthSecondPage;O B>3> GB>1K Pumpa <>3;0 G8B0BL 8 ?C1;8:>20BL =>2K5 A>>1I5=8O 2 20H pump.io 0::0C=B 2K 4>;6=K ?@54>AB028BL Pumpa 4>ABC? G5@57 251 AB@0=8FC. Pumpa >B:@>25B 251 AB@0=8FC 4;O 20A - ?@>AB> A;54C9B5 8=AB@C:F8O< 8 A:>?8@C9B5 B5:AB>2CN AB@>G:C <b>verifier</b> 8 2AB02LB5 5Q 2 ?>;5 A=87C >B B5:AB0. (Token C65 4>;65= 1KBL 70?>;=5= 02B><0B8G5A:8.)HIn order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy & paste the verifier text string back into the field below. (The token should be automatically pre-filled.)OAuthSecondPage Token:Token:OAuthSecondPageVerifier: Verifier:OAuthSecondPage42B>@87CN ?>;L7>20B5;O ...Authorising user ... OAuthWizardH81:0 A5B8: Network error:  OAuthWizard>H81:0 0CB5=B8D8:0F88 8;8 A5B8! Network or authentication error! OAuthWizardN;85=B CA?5H=> 70@538AB@8@>20= =0 [%1].'Registered client to [%1] successfully. OAuthWizard. 538AB@8@CN :;85=B0 ...Registering client ... OAuthWizardRe: Re:  ObjectWidget"?>:070BL :>=B5:AB show context ObjectWidgetL %1 =5 2 A>AB>O=88 ?@>25@8BL ?>4;8==>ABL A5@25@0. -B0 >H81:0 <>65B 7=0G8BL, GB> :B>-B> ?KB05BAO 2K40BL A51O 70 A5@25@, 8;8 GB> 04<8=8AB@0B>@ A5@25@0 A>25@H8; >H81:C.  %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. PumpApp%1 &:=> %1 &WindowPumpApp(%1 >B:><<5=B8@>20;: %1 commented: PumpApp%1 =0?8A0;:  %1 wrote: PumpApp& ?@>3@0<<5&AboutPumpApp&>=B5:AB&ContextPumpApp&@O<K5&DirectPumpApp&>4?8AG8:8 &FollowersPumpApp&><>IL&HelpPumpApp&E>4OI85&InboxPumpApp&#?><8=0=8O &MentionsPumpApp &Pumpa&PumpaPumpApp05@570&3@C78BL A>>1I5=8O&Reload timelinePumpApp&:;04:8&TabsPumpAppF<b>5157>?0A=>5 SSL A>548=5=85!</b> Untrusted SSL connection!PumpApp<p>!>>1I09B5 >1 >H81:0E 8 70?@0H8209B5 =>2K5 2>7<>6=>AB8 ?> 04@5AC <a href="%5">%5</a>.</p>?

      Report bugs and feature requests at %5.

      PumpAppF@>AB>9 pump.io :;85=B =0 >A=>25 Qt!A simple Qt-based pump.io client.PumpApp&:B82=>ABL A&ctivitiesPumpApp %1About %1PumpApp  &Qt About &QtPumpApp:B82=>ABL ActivitiesPumpApp0:@KBL 2:;04:C Close tabPumpApp Ctrl+DCtrl+DPumpApp Ctrl+LCtrl+LPumpApp Ctrl+NCtrl+NPumpApp Ctrl+OCtrl+OPumpApp Ctrl+QCtrl+QPumpApp Ctrl+RCtrl+RPumpApp Ctrl+WCtrl+WPumpApp &KE>4E&xitPumpApp~2548B5 webfinger ID G5;>25:0, =0 :>B>@>3> E>B8B5 ?>4?8A0BLAO: (Enter webfinger ID of person to follow: PumpAppH81:0: Error: PumpAppAB5:05B:  Expires: PumpApp&71@0==>5 F&avoritesPumpApp8&>4?8A0BLAO =0 ?>;L7>20B5;OF&ollow an accountPumpApp&>4?8A:8 F&ollowingPumpApp71@0==>5 FavoritesPumpAppFi&rehose Fi&rehosePumpAppFirehoseFirehosePumpApp@>4?8A:0 =0 pump.io ?>;L7>20B5;OFollow pump.io userPumpApp>4?8AG8:8 FollowersPumpApp>4?8A:8 FollowingPumpApp!?@OB0BLHidePumpApph5:>@@5:B=K9 ?>;L7>20B5;L (=5 <>3C ?@>25@8BL A09B): "Invalid user (cannot check site): PumpApp65:>@@5:B=K9 ?>;L7>20B5;L: Invalid user: PumpApp5< 2K40=:  Issued by: PumpApp><C 2K40=:  Issued to: PumpApp@03@C78BL 1>;55 AB0@K5 A>>1I5=8OLoad older in timelinePumpApp03@C7:0 ... Loading ...PumpAppMD5 AC<<0:  MD5 digest: PumpApp&564C B5< Mean&whilePumpAppNH81:0 A5B8 8;8 02B>@870F88 [%1/%2] %3.*Network or authorisation error [%1/%2] %3.PumpApp >2>5 &!>>1I5=85 New &NotePumpApp0AB@>9:8 PreferencesPumpAppC1;8G=>PublicPumpApp>B>2>!Ready!PumpApp&&!>>1I8BL >1 >H81:5Report &bug onlinePumpAppH81:0 SSL: SSL Error: PumpApp.SSL A5@B8D8:0B !5@25@0 SSL Server certificate. PumpApp>:070BLShowPumpApp*!09B 5IQ =5 =0AB@>5=!Site not configured yet!PumpAppZ@>AB8B5, MB> 4065 =5 ?>E>65 =0 webfinger ID!2Sorry, that doesn't even look like a webfinger ID!PumpAppZ@>AB8B5, 2K C65 ?>4?8A0=K =0 MB>3> G5;>25:0!-Sorry, you are already following that person!PumpApp,#A?5H=> ?>4?8A0;AO =0 Successfully followed PumpApp*#A?5H=> >B?8A0;AO >B Successfully unfollowed PumpApp>5 <>3C >?C1;8:>20BL A>>1I5=85!Unable to post message!PumpApp<5 <>3C 703@C78BL 87>1@065=85!Unable to upload image!PumpApp8# 20A %Ln =>2>5 C254><;5=85.8# 20A %Ln =>2KE C254><;5=8O.8# 20A %Ln =>2KE C254><;5=89.!You have %Ln new notification(s).PumpApp0H ?&@>D8;L Your &profilePumpApp::0C=BAccountPumpaSettingsDialogA5 A>>1I5=8OAnythingPumpaSettingsDialog 7<5=8BL 0::0C=BChange accountPumpaSettingsDialog060B85 "7<5=8BL 0::0C=B" ?>2B>@=> 70?CAB8B =0AB@>9:8 0CB5=B8D8:0F88 4;O =>2>3> pump.io 0::0C=B0.  @57C;LB0B5 B5:CI85 :;NG8 ?>4:;NG5=8O 1C4CB C40;5=K, ?>A:>;L:C Pumpa ?>445@68205B >4=>2@5<5==> B>;L:> >48= 0::0C=B.Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time.PumpaSettingsDialog@ 40==K9 <><5=B ?>4:;NGQ= :0: %1Currently logged in as %1.PumpaSettingsDialog*">?8O" ?> C<>;G0=8N: Default "CC":PumpaSettingsDialog("><C" ?> C<>;G0=8N: Default "To":PumpaSettingsDialog.">;L:> ?@O<K5 A>>1I5=8O Direct onlyPumpaSettingsDialog>@O<K5 A>>I15=8O 8;8 C?><8=0=8ODirect or mentionPumpaSettingsDialogR@O<K5 A>>1I5=8O, C?><8=0=8O 8;8 2E>4OI85Direct, mention or inboxPumpaSettingsDialog>4?8AG8:8 FollowersPumpaSettingsDialog<>4A25G820BL 8:>=:C 2 B@55 =0:Highlight tray icon on:PumpaSettingsDialog=B5@D59A InterfacePumpaSettingsDialog8:>340NeverPumpaSettingsDialog: 40==K9 <><5=B =5 ?>4:;NG5=.Not logged in currently.PumpaSettingsDialog#254><;5=8O NotificationsPumpaSettingsDialog6A?;K20NI85 C254><;5=8O =0:Popup notification on:PumpaSettingsDialogC1;8G=>PublicPumpaSettingsDialogT>:07K20BL :>;8G5AB2> A8<2>;>2 2 A>>1I5=88Show message character countPumpaSettingsDialog@=B5@20; >1=>2;5=8O (2 <8=CB0E):Update interval (in minutes):PumpaSettingsDialog.>102;OBL 8:>=:C 2 B@59Use icon in system trayPumpaSettingsDialog#40;5=> %1 Deleted %1 QASObject%n 45=L =0704%n 4=O =0704%n 4=59 =0704 %n days agoQObject%n G0A =0704%n G0A0 =0704%n G0A>2 =0704 %n hours agoQObject%n <8=CBC =0704%n <8=CBK =0704%n <8=CB =0704%n minutes agoQObject%n <5AOF =0704%n <5AOF0 =0704 %n <5AOF52 =0704 %n months agoQObject%n =545;N =0704%n =545;8 =0704%n =545;L =0704 %n weeks agoQObject%n 3>4 =0704%n 3>40 =0704%n ;5B =0704 %n years agoQObject,=5A:>;L:> A5:C=4 =0704a few seconds agoQObject>48= 45=L =0704>48= 45=L =0704>48= 45=L =0704 one day agoQObject>48= G0A =0704>48= G0A =0704>48= G0A =0704 one hour agoQObject<8=CBC =0704one minute agoQObject >48= <5AOF =0704 >48= <5AOF =0704 >48= <5AOF =0704 one month agoQObject">4=C =545;N =0704">4=C =545;N =0704">4=C =545;N =0704 one week agoQObject>48= 3>4 =0704>48= 3>4 =0704>48= 3>4 =0704 one year agoQObject ) , pumpa-0.9.2/translations/pumpa_fr.ts0000644000175000017500000014674012653206625016263 0ustar matsmats ActivityWidget via %1 via %1 To: À: CC: CC: Public Public ActorWidget stop following arrêter de suivre follow suivre stop minimising posts auto-minimise posts Are you sure you want to stop following %1? CollectionWidget Load older Plus anciens EditProfileDialog &Change picture &Changer d'image Real name Hometown Bio Select Image Choisir une image Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Fichers images (*.png *.jpg *.jpeg *.gif);;Tous les fichiers (*.*) Sorry! Désolé! That file didn't appear to be an image. Ce fichier ne semble pas être une image. FileDownloader Network error: Erreur de connexion: Network error: too many redirections! Unable to download %1 (Error #%2). Incapable de télécharger %1 (Erreur #%2). Could not open file %1 for writing: Échec de l'ouverture du fichier %1 en écriture: FullObjectWidget delete supprimer edit comment commenter [No description] [Pas de déscription] at %1 à %1 Profile last updated %1 updated %1 at %1 time when post was published à %1 unlike ne plus aimer like aimer share partager stop following arrêter de suivre follow suivre follow %1 You like this. and %Ln other person(s) shared this. Many persons shared a partagé ceci. shared this. One person shared a partagé ceci. %Ln person(s) shared this. Show all replies post Name of object type. note Name of object type. comment Name of object type. commenter image Name of object type. %1 like this. aime ou aiment ? comment faire la disctinction entre likes->onlyYou() et nl > 1 ? %1 aime. comment Button to comment on a post commenter at %1 location of person à %1 %1 likes this. %1 aime. and 1 other person et une autre personne and %1 other persons et %1 autres personnes shared this. a partagé ceci. 1 person shared this. 1 personne a partagé ceci. %1 persons shared this. %1 personnes ont partagé ceci. Show all %1 replies Afficher les %1 réponses Are you sure you want to delete this %1? Êtes vous sûr de vouloir supprimer ce %1 ? Share this %1 by %2? Are you sure you want to stop following Êtes vous sûr de vouloir arrêter de suivre MessageEdit Spelling suggestions... MessageRecipients - MessageWindow [markup] [markup] Public Public Followers Abonnés To: À: Cc: Cc: &Remove picture &Supprimer l'image + &To + &Cc Picture title (optional) Titre de l'image (facultatif) Use Markdown [help] Title (optional) Cancel Annuler Send message Envoyer le message Ca&ncel &Preview &Send message Select recipient (To) Select recipient (Cc) Post a note Écrire une note Post a reply Écrire une réponse &Send comment &Send post Edit object Edit post Edit comment Edit image post &Update object &Update post &Update comment &Update image post Select Image Choisir une image Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Fichers images (*.png *.jpg *.jpeg *.gif);;Tous les fichiers (*.*) Sorry! Désolé! That file didn't appear to be an image. Ce fichier ne semble pas être une image. &Add picture &Ajouter une image &Change picture &Changer d'image Characters: %1 OAuthFirstPage Welcome to Pumpa! Bienvenue sur Pumpa! <p>In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>When you are done enter your new pump.io account id below in the form of <b>username@servername</b>.</p> <p>Pour utiliser pump.io, vous devez d'abord créer un compte sur un serveur pump.io. Si vous n'avez pas encore fait cela, faites le maintenant en essayant un des serveurs public existant: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>Lorsque vous avez terminé, entrez votre nouveau compte pump.io sous le format <b>utilisateur@nom-du-serveur</b>.</p> <b>Your pump.io account id:</b> <b>Votre identité pump.io:</b> Use secure connection (recommended) Next Suivant OAuthSecondPage Authorise Pumpa Autoriser Pumpa In order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy &amp; paste the <b>verifier</b> text string back into the field below. (Thetoken should be automatically pre-filled.) Comme l'interface web n'est pas enore traduite, il est sans doute préférable de laisser les termes en anglais pour éviter la confusion Pour permettre a Pumpa d'être capable de lire et envoyer de nouveaux messages sur votre compte pump.io, vous devez autoriser Pumpa via la page web. Pumpa va ouvrir la page pour vous, suivez simplement les instructions et copiez &amp; collez le texte pour <b>verifier</b> dans le champ ci-dessous. (Le token doit en principe être automatiquement pré-remplit.) In order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy &amp; paste the <b>verifier</b> text string back into the field below. (The token should be automatically pre-filled.) Token: Token: Verifier: Verifier: OAuthWizard Registering client ... Enregistrement du client... Network error: Erreur réseau: Registered client to [%1] successfully. Le client a bien été enregistré à [%1]. Authorising user ... Autorisation de l'utilisateur en cours... Network or authentication error! Erreur de connexion ou d'autentification! ObjectWidget Re: Re: show context afficher le contexte PumpApp &Inbox Récept&ion &Mentions &Mentions &Direct &Directs Mean&while &Activités Copyright &copy; 2013-2015 Mats Sj&ouml;berg Fi&rehose &Firehose &Followers A&bonnés F&ollowing Ab&onnements You have new messages. Vous avez de nouveaux messages. Error: Erreur: %1 &Window %1 &Fenêtre Hide Masquer Show Afficher E&xit &Fermer Ctrl+Q Ctrl+Q Preferences Préférences &Reload timeline &Recharger le flux Ctrl+R Ctrl+R Load older in timeline Charger des éléments plus anciens dans le flux Ctrl+O Ctrl+O F&ollow an account &Suivre un compte Ctrl+L Ctrl+L Your &profile &About &A propos About &Qt À propos de &Qt New &Note Nouvelle &Note Ctrl+N Ctrl+N Ctrl+D Followers Abonnés SSL Error: %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. SSL Server certificate. Issued to: Issued by: Effective: Expires: MD5 digest: <b>Untrusted SSL connection!</b> Public Public You have %Ln new notification(s). %1 commented: %1 wrote: Report &bug online Close tab Ctrl+W Firehose Following Favorites Activities &Pumpa &Pumpa &Tabs &Help &Aide <p>Pumpa 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 3 of the License, or (at your option) any later version.</p><p>Pumpa 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.</p><p>You should have received a copy of the GNU General Public License along with Pumpa. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> non traduite car pas certain de la valeur juridique des traductions de licence <p>Pumpa 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 3 of the License, or (at your option) any later version.</p><p>Pumpa 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.</p><p>You should have received a copy of the GNU General Public License along with Pumpa. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> <p>The <a href="https://github.com/kypeli/kQOAuth">kQOAuth library</a> is copyrighted by <a href="http://www.johanpaul.com/">Johan Paul</a> and licensed under LGPL 2.1.</p><p>The <a href="https://github.com/vmg/sundown">sundown Markdown library</a> is copyrighted by Natacha Port&eacute;, Vicent Marti and others, and <a href="https://github.com/vmg/sundown#license">permissively licensed</a>.</p><p>The Pumpa logo was <a href="http://opengameart.org/content/fruit-and-veggie-inventory">created by Joshua Taylor</a> for the <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0. <p>La librairie <a href="https://github.com/kypeli/kQOAuth">kQOAuth library</a> est la propriété intelectuelle de <a href="http://www.johanpaul.com/">Johan Paul</a> et publié sous licence LGPL 2.1.</p><p>La <a href="https://github.com/vmg/sundown">librairie sundown Markdown</a> est la propriété de Natacha Port&eacute;, Vicent Marti et autres, et publié sous <a href="https://github.com/vmg/sundown#license">license permissive</a>.</p><p>Le logo de Pumpa a été créé par <a href="http://opengameart.org/content/fruit-and-veggie-inventory">Joshua Taylor</a> pour la <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.Le logo est la propriété intellectuelle de l'artiste et publié sous la licence double CC-BY-SA 3.0 et GNU GPL 3.0. Copyright &copy; 2013 Mats Sj&ouml;berg.</p> Copyright &copy; 2013 Mats Sj&ouml;berg.</p> <p>Report bugs and feature requests at <a href="%5">%5</a>.</p> <p>Rapports de bugs et demande de nouvelles fonctionnalités à <a href="%5">%5</a>.</p> A simple Qt-based pump.io client. Un simple client pump.io en Qt. About %1 À propos %1 Site not configured yet! Site pas encore configuré! Follow pump.io user Suivre un utilisateur de pump.io Enter webfinger ID of person to follow: Entez l'adresse de la personne a suivre: Sorry, that doesn't even look like a webfinger ID! Désolé, cela ne ressemble même pas à une adresse au format webfinger! Sorry, you are already following that person! Désolé, vous suivez déjà cette personne! Invalid user: Utilisateur invalide: Invalid user (cannot check site): Utilisateur invalide (ne peux pas vérifier le site): &Context &Contexte F&avorites A&ctivities Loading ... Chargement... Ready! Prêt! Unable to post message! Incapable d'envoyer un message! Unable to upload image! Network or authorisation error [%1/%2] %3. Network or authorisation error [%1/%2]. Erreur réseau ou d'autorisation [%1/%2]. Successfully followed Demande d'abonnement réussie Successfully unfollowed Demande de désabonnement réussie PumpaSettingsDialog Account Compte Not logged in currently. Actuellement déconnecté. Change account Changer de compte Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time. Cliquer sur "Changer de compte" va redémarrer le processus d'autentification pour le nouveau compte pump.io. Ceci va supprimer les informations de connexion actuelles comme Pumpa ne supporte pas le multi-compte. Interface Interface Update interval (in minutes): Interval de mise à jour (en minutes): Public Public Followers Abonnés Default recipient: Destinataire par défaut: Default "To": Default "CC": Use icon in system tray Icone dans la bare de tâche Show message character count Notifications Notifications Never Jamais Direct only Seulement pour les messages directs Direct or mention Messages directs ou mentions Direct, mention or inbox Messages directs, mentions ou réception Anything Tout Highlight tray icon on: Notification dans l'icone de la bare de tâche pour: Popup notification on: Notification dans une popup pour: Currently logged in as %1. Actuellement connecté comme %1. QASObject Deleted %1 QObject a few seconds ago il y a quelques secondes one minute ago il y a une minute %n minutes ago one hour ago %n hours ago one day ago %n days ago one week ago %n weeks ago one month ago %n months ago one year ago %n years ago %1 minutes ago il y a %1 minutes 1 hour ago il y a 1 heure %1 hours ago il y a %1 heures pumpa-0.9.2/translations/pumpa_de.qm0000644000175000017500000005260612653206625016230 0ustar matsmatsx>`U`'wl@02k$ HNG|VхZ@7^~AeE~Aeu!)@!R?$X^M': @ #!K #n )aJY +"e: 0p, =I lf1 ( : n 1 ľE# ? ?*- .mD TG) TG UoLF eT- n   - `K Pu ȗF `9 oK p3 L 3d 66p8b 7p8 F(@ G, I :N0 X XY Zc. e4 *D =*Dq ΡJ N'y ; N : E /N G /y /y }0 d I  X iFC5 mC59 n4J);MnBq7 /Ŕ)>]NsiOM ber %1 via %1ActivityWidgetCC:CC:ActivityWidgetffentlichPublicActivityWidgetan:To:ActivityWidgetlSind Sie sicher, dass Sie %1 nicht mehr folgen wollen?+Are you sure you want to stop following %1? ActorWidget>Beitrge automatisch minimierenauto-minimise posts ActorWidget Folgenfollow ActorWidget"Nicht mehr folgenstop following ActorWidget>Beitrge minimieren ausschaltenstop minimising posts ActorWidget*ltere Beitrge laden Load olderCollectionWidgetBild &ndern&Change pictureEditProfileDialogpBilddatei (*.png *.jpg *.jpeg *.gif);;Alle Dateien (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*)EditProfileDialogBild auswhlen Select ImageEditProfileDialogEntschuldigung!Sorry!EditProfileDialog`Die ausgewhlte Datei scheint kein Bild zu sein.'That file didn't appear to be an image.EditProfileDialogjDatei %1 konnte zum Schreiben nicht geffnet werden: $Could not open file %1 for writing: FileDownloader Netzwerkfehler: Network error: FileDownloaderVDownload von %1 nicht mglich (Fehler #%2)."Unable to download %1 (Error #%2).FileDownloader%1 gefllt das. %1 like this.FullObjectWidget%1 gefllt das.%1 likes this.FullObjectWidget,von %Ln Person geteilt0von %Ln Personen geteilt%Ln person(s) shared this.FullObjectWidgetTSind Sie sicher das sie %1 lschen wollen?(Are you sure you want to delete this %1?FullObjectWidget6letzte Profilnderung am %1Profile last updated %1FullObjectWidget"%1 von %2 teilen?Share this %1 by %2?FullObjectWidget0Alle Kommentare anzeigenShow all repliesFullObjectWidget Dir gefllt das.You like this.FullObjectWidget([Keine Beschreibung][No description]FullObjectWidget,und %Ln weitere Person2und %Ln weiteren Personenand %Ln other person(s)FullObjectWidget@ %1location of personat %1FullObjectWidget@ %1time when post was publishedat %1FullObjectWidgetkommentierenButton to comment on a postcommentFullObjectWidgetkommentierenName of object type.commentFullObjectWidgetlschendeleteFullObjectWidgetbearbeiteneditFullObjectWidget folgefollowFullObjectWidget%1 folgen follow %1FullObjectWidgetBildName of object type.imageFullObjectWidgetlikelikeFullObjectWidgetMitteilungName of object type.noteFullObjectWidgetBeitragName of object type.postFullObjectWidget teilenshareFullObjectWidgetteilten das.Many persons shared shared this.FullObjectWidgetteilten das.One person shared shared this.FullObjectWidgetentfolgestop followingFullObjectWidget unlikeunlikeFullObjectWidgetgendert %1 updated %1FullObjectWidget4Rechtschreibvorschlge ...Spelling suggestions... MessageEdit--MessageRecipients&Bild einfgen &Add picture MessageWindowBild &ndern&Change picture MessageWindow&Vorschau&Preview MessageWindowBild &lschen&Remove picture MessageWindow"Kommentar &senden &Send comment MessageWindow"Nachricht &senden &Send message MessageWindowBeitrag &senden &Send post MessageWindow0Kommentar akt&ualisieren&Update comment MessageWindow8Bildnachricht akt&ualisieren&Update image post MessageWindow*Objekt akt&ualisieren&Update object MessageWindow0Nachricht akt&ualisieren &Update post MessageWindow + &Cc+ &Cc MessageWindow + &To+ &To MessageWindowAbbreche&nCa&ncel MessageWindowCc:Cc: MessageWindow(Kommentar bearbeiten Edit comment MessageWindow0Bildnachricht bearbeitenEdit image post MessageWindow"Objekt bearbeiten Edit object MessageWindow$Beitrag bearbeiten Edit post MessageWindowpBilddatei (*.png *.jpg *.jpeg *.gif);;Alle Dateien (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) MessageWindowNeue Mitteilung Post a note MessageWindow"Antwort verfassen Post a reply MessageWindowBild auswhlen Select Image MessageWindow0Empfnger auswhlen (Cc)Select recipient (Cc) MessageWindow0Empfnger auswhlen (To)Select recipient (To) MessageWindowEntschuldigung!Sorry! MessageWindow`Die ausgewhlte Datei scheint kein Bild zu sein.'That file didn't appear to be an image. MessageWindow Titel (optional)Title (optional) MessageWindowTo:To: MessageWindow$Markdown verwenden Use Markdown MessageWindow[Hilfe][help] MessageWindow><b>Ihre pump.io Account-ID:</b>Your pump.io account id:OAuthFirstPage<p>Um pump.io nutzen zu knnen, bentigen Sie einen Account auf einem pump.io-Server. Wenn Sie noch keinen Account besitzen, knnen Sie sich einfach an einem der ffentlichen pump.io-Server unter folgendem Link registrieren:</p> <a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a><p>Wenn Sie einen Account erstellt haben oder bereits ein pump.io-Konto besitzen, tragen Sie Ihre Account-ID in dem Format <b>benutzername@servername</b> in dem unten angezeigten Feld ein.</p>p

      In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers:
      http://pump.io/tryit.html.

      When you are done enter your new pump.io account id below in the form of username@servername.

      OAuthFirstPageNextOAuthFirstPagePSichere Verbindung verwenden (empfohlen)#Use secure connection (recommended)OAuthFirstPage(Willkommen zu Pumpa!Welcome to Pumpa!OAuthFirstPage$Pumpa autorisierenAuthorise PumpaOAuthSecondPageDamit Pumpa neue Nachrichten lesen und verfassen kann, mssen Sie den Zugriff auf Ihren pump.io-Account gestatten. Pumpa wird dazu eine Webseite des pump.io-Servers aufrufen, deren Anweisungen Sie folgen. Um den Anmeldevorgang abzuschlieen, kopieren Sie den <b>Verifier</b> in das obere Feld. (Der Token sollte von Pumpa automatisch ausgefllt werden, sobald sich die Webseite ffenet)HIn order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy & paste the verifier text string back into the field below. (The token should be automatically pre-filled.)OAuthSecondPage Token:Token:OAuthSecondPageVerifier: Verifier:OAuthSecondPage0Autorisiere Benutzer ...Authorising user ... OAuthWizard Netzwerkfehler: Network error:  OAuthWizardHNetzwerk- oder Autorisierungsfehler! Network or authentication error! OAuthWizardTAnmeldung des Clients an [%1] erfolgreich.'Registered client to [%1] successfully. OAuthWizard.Client regestrieren ...Registering client ... OAuthWizardRe: Re:  ObjectWidget Kontext anzeigen show context ObjectWidget %1 ist nicht in der Lage, die Identitt des Servers zu berprfen. Dieser Fehler knnte bedeuten, dass jemand versucht sich als dieser Server auszugeben, oder dass der Server-Administrator einen Fehler gemacht hat. %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. PumpApp%1 Fenster %1 &WindowPumpApp"%1 kommentierte: %1 commented: PumpApp%1 schreibt:  %1 wrote: PumpAppber&AboutPumpAppKontext&ContextPumpApp&Posteingang&DirectPumpApp &FollowersPumpApp &Hilfe&HelpPumpApp&Nachrichten&InboxPumpApp&Vorschlge &MentionsPumpApp&PumpaPumpApp&Beit&rge neu laden&Reload timelinePumpApp&TabsPumpApp\<b>Nicht vertrauenswrdige SSL-Verbindung!</b> Untrusted SSL connection!PumpApp<p> Fehler und Verbesserungsvorschlge bitte an <a href="%5">%5</a> melden!</p>?

      Report bugs and feature requests at %5.

      PumpAppTEin einfacher Qt-basierter pump.io-Client.!A simple Qt-based pump.io client.PumpAppAktivitten A&ctivitiesPumpAppber %1About %1PumpAppber &Qt About &QtPumpAppAktivitten ActivitiesPumpApp Strg+DCtrl+DPumpApp Strg+FCtrl+LPumpApp Strg+NCtrl+NPumpApp Strg+LCtrl+OPumpApp Strg+XCtrl+QPumpApp Strg+RCtrl+RPumpAppCtrl+WPumpApp E&xitE&xitPumpAppEffektiv:  Effective: PumpAppGeben Sie die Webfinger-ID der Person ein, der Sie folgen wollen: (Enter webfinger ID of person to follow: PumpAppFehler: Error: PumpAppGltig bis:  Expires: PumpAppF&avoriten F&avoritesPumpAppNutzer &folgenF&ollow an accountPumpApp F&ollowingPumpAppFavoriten FavoritesPumpAppFi&rehose Fi&rehosePumpAppFirehosePumpApp*pump.io Nutzer folgenFollow pump.io userPumpAppFollowers FollowersPumpApp FollowingPumpAppVersteckenHidePumpApprUngltiger Benutzer (Seite kann nicht berprft werden): "Invalid user (cannot check site): PumpApp*Ungltiger Benutzer: Invalid user: PumpApp$Herausgegeben von: Issued by: PumpApp"Ausgestellt fr:  Issued to: PumpApp*ltere Beitrge ladenLoad older in timelinePumpAppLade ... Loading ...PumpAppMD5-Digest:  MD5 digest: PumpAppN&euigkeiten Mean&whilePumpApp`Netzwerk- oder Autorisierungsfehler! [%1/%2] %3.*Network or authorisation error [%1/%2] %3.PumpApp &Neue Mitteilung New &NotePumpAppEigenschaften PreferencesPumpAppffentlichPublicPumpAppFertig!Ready!PumpAppSSL Fehler:  SSL Error: PumpApp*SSL Server Zertifkat.SSL Server certificate. PumpAppAnzeigenShowPumpApp:Seite ist nicht konfiguriert!Site not configured yet!PumpAppEntschuldigung, aber das sieht leider nicht nach einer Webfinger-ID aus!2Sorry, that doesn't even look like a webfinger ID!PumpAppbEntschuldigung! Sie folgen dieser Person bereits!-Sorry, you are already following that person!PumpApp Du folgst jetzt Successfully followed PumpApp,Entfolgen erfolgreich Successfully unfollowed PumpAppVNachricht kann nicht verffentlicht werden!Unable to post message!PumpAppFBild kann nicht hochgeladen werden!Unable to upload image!PumpApp KontoAccountPumpaSettingsDialogIrgendetwasAnythingPumpaSettingsDialogKonto ndernChange accountPumpaSettingsDialogWenn sie auf "Konto ndern" klicken, wird der Authentifizierungsprozess neu gestartet. Alle Anmeldedaten zu Ihrem pump.io-Account werden vom System entfernt. Mit Pumpa kann zur Zeit nur ein Account zur selben Zeit genutzt werden.Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time.PumpaSettingsDialog8Zur Zeit eingeloggt als: %1.Currently logged in as %1.PumpaSettingsDialog6Standardempfnger fr "CC": Default "CC":PumpaSettingsDialog6Standardempfnger fr "To": Default "To":PumpaSettingsDialogNur direkt Direct onlyPumpaSettingsDialog6Direkt oder mit VorschlgenDirect or mentionPumpaSettingsDialog`Direkt, mit Vorschlgen oder aus dem PosteingangDirect, mention or inboxPumpaSettingsDialog FollowersPumpaSettingsDialogTIcon in der Statusleiste hervorheben wenn:Highlight tray icon on:PumpaSettingsDialogSchnittstelle InterfacePumpaSettingsDialogNiemalsNeverPumpaSettingsDialog4Zur Zeit nicht eingeloggt.Not logged in currently.PumpaSettingsDialog$Benachrichtigungen NotificationsPumpaSettingsDialog:Popup-Benachrichtigungen bei:Popup notification on:PumpaSettingsDialogffentlichPublicPumpaSettingsDialogLAktualisierungsintervall (in Minuten):Update interval (in minutes):PumpaSettingsDialogBIcon in der Statusleiste anzeigenUse icon in system trayPumpaSettingsDialogGelscht am %1 Deleted %1 QASObjectvor %n Tagenvor %n Tagen %n days agoQObjectvor %n Stundenvor %n Stunden %n hours agoQObjectvor %n Minutevor %n Minuten%n minutes agoQObjectvor %n Monatenvor %n Monaten %n months agoQObjectvor %n Wochenvor %n Wochen %n weeks agoQObjectvor %n Jahrenvor %n Jahren %n years agoQObject*vor ein paar Sekundena few seconds agoQObjectvor einem Tagvor einem Tag one day agoQObject vor einer Stunde vor einer Stunde one hour agoQObject vor einer Minuteone minute agoQObjectvor einem Monatvor einem Monat one month agoQObjectvor einer Wochevor einer Woche one week agoQObjectvor einem Jahrvor einem Jahr one year agoQObjectpumpa-0.9.2/translations/pumpa_en.ts0000644000175000017500000013162312653206625016250 0ustar matsmats ActivityWidget via %1 To: CC: Public ActorWidget stop following follow stop minimising posts auto-minimise posts Are you sure you want to stop following %1? CollectionWidget Load older EditProfileDialog &Change picture Real name Hometown Bio Select Image Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Sorry! That file didn't appear to be an image. FileDownloader Network error: Network error: too many redirections! Unable to download %1 (Error #%2). Could not open file %1 for writing: FullObjectWidget delete edit comment Button to comment on a post [No description] at %1 location of person Profile last updated %1 updated %1 at %1 time when post was published unlike like share stop following follow follow %1 You like this. %1 likes this. %1 like this. and %Ln other person(s) and one other person and %n other persons shared this. Many persons shared shared this. One person shared Show all replies post Name of object type. note Name of object type. comment Name of object type. image Name of object type. %Ln persons shared this. one person shared this. %n persons shared this. %Ln person(s) shared this. Are you sure you want to delete this %1? Share this %1 by %2? MessageEdit Spelling suggestions... MessageRecipients - MessageWindow Use Markdown [help] To: Cc: &Remove picture + &To + &Cc Title (optional) Ca&ncel &Preview &Send message Select recipient (To) Select recipient (Cc) Post a note Post a reply &Send comment &Send post Edit object Edit post Edit comment Edit image post &Update object &Update post &Update comment &Update image post Select Image Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Sorry! That file didn't appear to be an image. &Add picture &Change picture Characters: %1 OAuthFirstPage Welcome to Pumpa! <p>In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p><p>When you are done enter your new pump.io account id below in the form of <b>username@servername</b>.</p> <b>Your pump.io account id:</b> Use secure connection (recommended) Next OAuthSecondPage Authorise Pumpa In order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy &amp; paste the <b>verifier</b> text string back into the field below. (The token should be automatically pre-filled.) Token: Verifier: OAuthWizard Registering client ... Network error: Registered client to [%1] successfully. Authorising user ... Network or authentication error! ObjectWidget Re: show context PumpApp &Inbox &Mentions &Direct Mean&while Copyright &copy; 2013-2015 Mats Sj&ouml;berg Fi&rehose Error: %1 &Window Hide Show E&xit Ctrl+Q Preferences &Reload timeline Ctrl+R Load older in timeline Ctrl+O F&ollow an account Ctrl+L &About About &Qt New &Note Ctrl+N Ctrl+D Followers SSL Error: %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. SSL Server certificate. Issued to: Issued by: Effective: Expires: MD5 digest: <b>Untrusted SSL connection!</b> Public You have %Ln new notification(s). %1 commented: %1 wrote: Your &profile Report &bug online Close tab Ctrl+W Firehose Following Favorites Activities &Pumpa &Tabs &Help <p>Pumpa 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 3 of the License, or (at your option) any later version.</p><p>Pumpa 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.</p><p>You should have received a copy of the GNU General Public License along with Pumpa. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> <p>The <a href="https://github.com/kypeli/kQOAuth">kQOAuth library</a> is copyrighted by <a href="http://www.johanpaul.com/">Johan Paul</a> and licensed under LGPL 2.1.</p><p>The <a href="https://github.com/vmg/sundown">sundown Markdown library</a> is copyrighted by Natacha Port&eacute;, Vicent Marti and others, and <a href="https://github.com/vmg/sundown#license">permissively licensed</a>.</p><p>The Pumpa logo was <a href="http://opengameart.org/content/fruit-and-veggie-inventory">created by Joshua Taylor</a> for the <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0. <p>Report bugs and feature requests at <a href="%5">%5</a>.</p> A simple Qt-based pump.io client. About %1 Site not configured yet! Follow pump.io user Enter webfinger ID of person to follow: Sorry, that doesn't even look like a webfinger ID! Sorry, you are already following that person! Invalid user: Invalid user (cannot check site): &Context &Followers F&ollowing F&avorites A&ctivities Loading ... Ready! Unable to post message! Unable to upload image! Network or authorisation error [%1/%2] %3. Successfully followed Successfully unfollowed PumpaSettingsDialog Account Not logged in currently. Change account Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time. Interface Update interval (in minutes): Public Followers Default "To": Default "CC": Use icon in system tray Show message character count Notifications Never Direct only Direct or mention Direct, mention or inbox Anything Highlight tray icon on: Popup notification on: Currently logged in as %1. QASObject Deleted %1 QObject a few seconds ago one minute ago %n minutes ago one hour ago %n hours ago one day ago %n days ago one week ago %n weeks ago one month ago %n months ago one year ago %n years ago %n minute(s) ago %n minute ago %n minutes ago %n hour(s) ago %n hour ago %n hours ago pumpa-0.9.2/translations/pumpa_it.qm0000644000175000017500000006216612653206625016256 0ustar matsmats9xNU`3`&wrlO02k$ 7HNWVхPZ@GR~Ae/~Ae!!)!O@$^\&. P* # #n )aY +"eJ 0p+ =I lf@ ' J n7 1 ľT N ?) .mT< TG TGX Uo[~ eT, n   , `Z` Pu ȗV `I o[ pC \ 3d 66pHF 7pH F(P| A G+ I :N? ^/ Xx XY Zc- eD *S =*S ΡYs N&m K ]e J U /] Wt /y /y7 }0 dH I b X3 iFCE mCD nLZ o%. %E X] |1! [ jW.D%8UHBUZbqM' +,>4J)KMnq7 6Ŕ(MNsfi^% via %1 via %1ActivityWidgetCC:CC:ActivityWidgetPubblicoPublicActivityWidgetA:To:ActivityWidgetVSei sicuro di voler smettere di seguire %1?+Are you sure you want to stop following %1? ActorWidgetLminimizza automaticamente gli elementiauto-minimise posts ActorWidget seguifollow ActorWidget"smetti di seguirestop following ActorWidget@non minimizzare pi gli elementistop minimising posts ActorWidget&Carica meno recenti Load olderCollectionWidget Ca&mbia immagine&Change pictureEditProfileDialog|Files immagine (*.png *.jpg *.jpeg *.gif);;Tutti i files (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*)EditProfileDialog*Seleziona un'immagine Select ImageEditProfileDialog Scusa!Sorry!EditProfileDialogTQuesto file non sembra essere un'immagine.'That file didn't appear to be an image.EditProfileDialogZNon riesco ad aprire il file %1 in scrittura:$Could not open file %1 for writing: FileDownloaderErrore di rete:Network error: FileDownloaderNNon riesco a scaricare %1 (Errore #%2)."Unable to download %1 (Error #%2).FileDownloader6A %1 piace questo elemento. %1 like this.FullObjectWidget6A %1 piace questo elemento.%1 likes this.FullObjectWidgetP%n persona ha condiviso questo elemento.V%n persone hanno condiviso questo elemento.%Ln person(s) shared this.FullObjectWidgetRSei sicure di volere eliminare questo %1?(Are you sure you want to delete this %1?FullObjectWidget>Ultimo aggiornamento profilo %1Profile last updated %1FullObjectWidget6Condividi questo %1 via %2?Share this %1 by %2?FullObjectWidget0Mostra tutte le risposteShow all repliesFullObjectWidgetTi piace.You like this.FullObjectWidget*[Nessuna descrizione][No description]FullObjectWidget*e %n un'altra persona$e %n altre personeand %Ln other person(s)FullObjectWidget in %1location of personat %1FullObjectWidget in %1time when post was publishedat %1FullObjectWidgetcommentaButton to comment on a postcommentFullObjectWidgetcommentaName of object type.commentFullObjectWidgeteliminadeleteFullObjectWidgetmodificaeditFullObjectWidget seguifollowFullObjectWidgetsegue %1 follow %1FullObjectWidgetimmagineName of object type.imageFullObjectWidgetmi piacelikeFullObjectWidgetnotaName of object type.noteFullObjectWidgetmessaggioName of object type.postFullObjectWidgetcondividishareFullObjectWidget4condiviso questo elemento.Many persons shared shared this.FullObjectWidget4condiviso questo elemento.One person shared shared this.FullObjectWidget"smetti di seguirestop followingFullObjectWidgetnon mi piaceunlikeFullObjectWidgetaggiornato %1 updated %1FullObjectWidget6Suggerimenti ortografici...Spelling suggestions... MessageEdit--MessageRecipients$A&ggiungi immagine &Add picture MessageWindow Ca&mbia immagine&Change picture MessageWindowAnte&prima&Preview MessageWindow"&Rimuovi immagine&Remove picture MessageWindowInvia &commento &Send comment MessageWindow Invia me&ssaggio &Send message MessageWindowInvia po&st &Send post MessageWindow$Aggiorna c&ommento&Update comment MessageWindow.Aggiorna p&ost immagine&Update image post MessageWindow"Aggiorna &oggetto&Update object MessageWindowAggiorna p&ost &Update post MessageWindow + &Cc+ &Cc MessageWindow+ &A+ &To MessageWindowA&nnullaCa&ncel MessageWindowCc:Cc: MessageWindow"Modifica commento Edit comment MessageWindow,Modifica post immagineEdit image post MessageWindow Modifica oggetto Edit object MessageWindowModifica post Edit post MessageWindow|Files immagine (*.png *.jpg *.jpeg *.gif);;Tutti i files (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) MessageWindow"Pubblica una nota Post a note MessageWindow*Pubblica una risposta Post a reply MessageWindow*Seleziona un'immagine Select Image MessageWindow6Seleziona destinatario (Cc)Select recipient (Cc) MessageWindow4Seleziona destinatario (A)Select recipient (To) MessageWindow Scusa!Sorry! MessageWindowTQuesto file non sembra essere un'immagine.'That file didn't appear to be an image. MessageWindow$Titolo (opzionale)Title (optional) MessageWindowA:To: MessageWindowUsa Markdown Use Markdown MessageWindow[aiuto][help] MessageWindowF<b>L'id del tuo account pump.io</b>Your pump.io account id:OAuthFirstPage<p>Per utilizzare pump.io devi registrare un account su un server pump.io. Se non ne hai ancora uno, puoi ottenerlo visitando uno dei server pubblici esistenti: <br /><a href="http://pump.io/tryit.html">http://pump.io/tryit.html</a>.</p> <p>Quando hai fatto, inserisci il tuo id dell'account pump.io, scritto come <b>nomeutente@server</b>.</p>p

      In order to use pump.io you need to first register an account with a pump.io server. If you haven't done this yet you can do it now by trying out one of the existing public servers:
      http://pump.io/tryit.html.

      When you are done enter your new pump.io account id below in the form of username@servername.

      OAuthFirstPage AvantiNextOAuthFirstPageJUsa connessione sicura (raccomandato)#Use secure connection (recommended)OAuthFirstPage&Benvenuti su Pumpa!Welcome to Pumpa!OAuthFirstPageAutorizza PumpaAuthorise PumpaOAuthSecondPagePer far si che Pumpa sia in grado di leggere e postare nuovi messaggi sul tuo account pump.io, devi autorizzare Pumpa attraverso una pagina web. Pumpa aprir questa pagina per te - segui le istruzioni e copia &amp; incolla il codice di <b>verifica</b> (verifier), nel campo qui sotto (il Token dovrebbe essere automaticamente precompilato)HIn order for Pumpa to be able to read and post new messages to your pump.io account you need to grant Pumpa access via the web page. Pumpa will open the web page for you - just follow the instructions and copy & paste the verifier text string back into the field below. (The token should be automatically pre-filled.)OAuthSecondPage Token:Token:OAuthSecondPageVerifier: Verifier:OAuthSecondPage2Autorizzando l'utente ...Authorising user ... OAuthWizardErrore di rete:Network error:  OAuthWizardFErrore di rete o di autenticazione! Network or authentication error! OAuthWizardPClient registrato in [%1] correttamente.'Registered client to [%1] successfully. OAuthWizard8Registrazione del client ...Registering client ... OAuthWizardR:Re:  ObjectWidgetmostra contesto show context ObjectWidget %1 non in grado di verificare l'identit del server. Questo errore pu significare che qualcuno sta provando a sostituire l'identit del server, o che l'amministratore del server ha commesso qualche errore. %1 is unable to verify the identity of the server. This error could mean that someone is trying to impersonate the server, or that the server's administrator has made an error. PumpApp%1 &Finestra %1 &WindowPumpApp$%1 ha commentato: %1 commented: PumpApp%1 ha scritto:  %1 wrote: PumpAppA p&roposito&AboutPumpApp&Contesto&ContextPumpApp&Diretti&DirectPumpAppFollo&wers &FollowersPumpApp &Aiuto&HelpPumpApp$Messagg&i ricevuti&InboxPumpApp&Menzioni &MentionsPumpApp &Pumpa&PumpaPumpApp$&Ricarica timeline&Reload timelinePumpApp &Tabs&TabsPumpAppD<b>Connessione SSL non sicura!</b> Untrusted SSL connection!PumpApp<p>Pumpa free software: puoi redistribuirlo e/o modificarlo nei termini della licenza GNU General Public License come pubblicato dalla Free Software Foundation, nella versione 3 della licenza o (a tua scelta) una versione successiva.</p><p>Pumpa distribuito nella speranza che ti sia utile, ma SENZA NESSUNA GARANZIA; senza nessuna implicita garanzia di COMMERCIABILITA' o di ADEGUATEZZA PER UN PARTICOLARE SCOPO. Vedere la GNU General Public License per pi dettagli.</p><p>Dovresti aver ricevuto una copia della licenza GNU GPL con Pumpa. In caso contrario, visita <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p>

      Pumpa 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 3 of the License, or (at your option) any later version.

      Pumpa 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 Pumpa. If not, see http://www.gnu.org/licenses/.

      PumpApp<p>Riporta problemi e richieste di funzionalit su <a href="%5">%5</a>.</p>?

      Report bugs and feature requests at %5.

      PumpAppj<p>La <a href="https://github.com/kypeli/kQOAuth">libreria kQOAuth</a> propriet di <a href="http://www.johanpaul.com/">Johan Paul</a> ed licenziata come LGPL 2.1.</p><p>La <a href="https://github.com/vmg/sundown">libreria sundown Markdown </a> propriet di Natacha Port&eacute;, Vicent Marti e altri, e <a href="https://github.com/vmg/sundown#license">resa disponibile con licenza permissiva</a>.</p><p>Il logo di Pumpa stato <a href="http://opengameart.org/content/fruit-and-veggie-inventory">creato da Joshua Taylor</a> per la <a href="http://lpc.opengameart.org/">Liberated Pixel Cup</a>.Il logo propriet dell'artista ed doppiamente licenziato come CC-BY-SA 3.0 e GNU GPL 3.0.

      The kQOAuth library is copyrighted by Johan Paul and licensed under LGPL 2.1.

      The sundown Markdown library is copyrighted by Natacha Porté, Vicent Marti and others, and permissively licensed.

      The Pumpa logo was created by Joshua Taylor for the Liberated Pixel Cup.The logo is copyrighted by the artist and is dual licensed under the CC-BY-SA 3.0 license and the GNU GPL 3.0.PumpAppPUn semplice client pump.io basato su Qt.!A simple Qt-based pump.io client.PumpAppAttiv&it A&ctivitiesPumpApp"A proposito di %1About %1PumpApp$A proposito di &Qt About &QtPumpAppAttivit ActivitiesPumpAppjCopyright &copy; 2014 Mats Sj&ouml;berg {2013-2015 ?},Copyright © 2013-2015 Mats SjöbergPumpApp Ctrl+DCtrl+DPumpApp Ctrl+LCtrl+LPumpApp Ctrl+NCtrl+NPumpApp Ctrl+OCtrl+OPumpApp Ctrl+QCtrl+QPumpApp Ctrl+RCtrl+RPumpApp Ctrl+WCtrl+WPumpApp E&sciE&xitPumpAppEffettivo: Effective: PumpApprInserisci il webfinger ID della persona che vuoi seguire:(Enter webfinger ID of person to follow: PumpAppErrore:Error: PumpApp Scade: Expires: PumpAppPre&feriti F&avoritesPumpApp"Segui un'acc&ountF&ollow an accountPumpAppF&ollowing F&ollowingPumpAppPreferiti FavoritesPumpAppFi&rehose Fi&rehosePumpAppFirehoseFirehosePumpApp.Segui un utente pump.ioFollow pump.io userPumpAppFollowers FollowersPumpAppFollowing FollowingPumpAppNascondiHidePumpApphUtente non valido (non riesco a verificare il sito):"Invalid user (cannot check site): PumpApp$Utente non valido:Invalid user: PumpAppRilasciato da: Issued by: PumpAppRilasciato a: Issued to: PumpAppRCarica elementi pi vecchi nella timelineLoad older in timelinePumpAppCaricamento ... Loading ...PumpAppDigest MD5: MD5 digest: PumpApp&Nel frattempo Mean&whilePumpApp\Errore di rete o di autorizzazione [%1/%2] %3.*Network or authorisation error [%1/%2] %3.PumpApp&Nuova Nota New &NotePumpAppPreferenze PreferencesPumpAppPubblicoPublicPumpAppPronto!Ready!PumpAppErrore SSL:  SSL Error: PumpApp.Certificato Server SSL.SSL Server certificate. PumpApp MostraShowPumpApp8Sito non ancora configurato!Site not configured yet!PumpAppRScusa, questo non sembra un webfinger ID!2Sorry, that doesn't even look like a webfinger ID!PumpAppPScusa, stai gi seguendo questa persona!-Sorry, you are already following that person!PumpApp$Following riuscitoSuccessfully followed PumpApp@Smesso di seguire correttamente Successfully unfollowed PumpAppDNon riesco a postare il messaggio!Unable to post message!PumpApp@Impossibile caricare l'immagine!Unable to upload image!PumpAppAccountAccountPumpaSettingsDialog TuttoAnythingPumpaSettingsDialogCambia accountChange accountPumpaSettingsDialogCliccando "Cambia account" partir una nuova procedura di autenticazione per un nuovo account pump.io. Questo rimuovera le credenziali di accesso correnti, visto che attualmente Pumpa supporta solo un account per volta.Clicking "Change account" will run the authentication setup again for a new pump.io account. This will remove the current login credentials since Pumpa only supports one account at a time.PumpaSettingsDialog8Attualmente loggato come %1.Currently logged in as %1.PumpaSettingsDialog""CC" predefiniti: Default "CC":PumpaSettingsDialog "A" predefiniti: Default "To":PumpaSettingsDialog*Solo messaggi diretti Direct onlyPumpaSettingsDialog6Messaggi diretti e menzioniDirect or mentionPumpaSettingsDialog^Messaggi diretti, menzioni e messaggi in arrivoDirect, mention or inboxPumpaSettingsDialogFollowers FollowersPumpaSettingsDialogbEvidenzia l'icona nella barra delle notifiche se:Highlight tray icon on:PumpaSettingsDialogInterfaccia InterfacePumpaSettingsDialogMaiNeverPumpaSettingsDialog0Attualmente disconnesso.Not logged in currently.PumpaSettingsDialogNotifiche NotificationsPumpaSettingsDialog&Notifiche popup se:Popup notification on:PumpaSettingsDialogPubblicoPublicPumpaSettingsDialogPIntervallo di aggiornamento (in minuti):Update interval (in minutes):PumpaSettingsDialogBUsa l'icona nell'area di notificaUse icon in system trayPumpaSettingsDialogEliminato %1 Deleted %1 QASObject%n giorni fa%n giorni fa %n days agoQObject%n ore fa%n ore fa %n hours agoQObject%n minuti fa%n minuti fa%n minutes agoQObject%n mesi fa%n mesi fa %n months agoQObject%n settimane fa%n settimane fa %n weeks agoQObject%n anni fa%n anni fa %n years agoQObject pochi secondi faa few seconds agoQObjectun giorno fa%n giorni fa one day agoQObjectun'ora fa%n ore fa one hour agoQObjectun minuto faone minute agoQObjectun mese fa%n mesi fa one month agoQObject una settimana fa%n settimane fa one week agoQObjectun anno fa%n anni fa one year agoQObjectpumpa-0.9.2/pumpa.desktop0000644000175000017500000000035512653206625014065 0ustar matsmats[Desktop Entry] Version=1.0 Name=Pumpa Exec=pumpa %U Icon=pumpa Terminal=false Type=Application GenericName=pump.io client Comment=pump.io social networking Categories=Network;InstantMessaging; Keywords=pump.io;social network;microblog; pumpa-0.9.2/debian/0000755000175000017500000000000012653206625012567 5ustar matsmatspumpa-0.9.2/debian/install0000644000175000017500000000004212653206625014154 0ustar matsmatsicons/pumpa.xpm usr/share/pixmaps pumpa-0.9.2/debian/docs0000644000175000017500000000000712653206625013437 0ustar matsmatsREADME pumpa-0.9.2/debian/compat0000644000175000017500000000000212653206625013765 0ustar matsmats9 pumpa-0.9.2/debian/pumpa.manpages0000644000175000017500000000001512653206625015422 0ustar matsmatsdocs/pumpa.1 pumpa-0.9.2/debian/source/0000755000175000017500000000000012653206625014067 5ustar matsmatspumpa-0.9.2/debian/source/format0000644000175000017500000000001412653206625015275 0ustar matsmats3.0 (quilt) pumpa-0.9.2/debian/source/include-binaries0000644000175000017500000000037112653206625017230 0ustar matsmatstranslations/pumpa_en.qm translations/pumpa_es.qm translations/pumpa_fr.qm translations/pumpa_it.qm translations/pumpa_nvi.qm translations/pumpa_de.qm images/glyphicons_197_remove.png images/glyphicons_199_ban.png images/glyphicons_200_download.png pumpa-0.9.2/debian/rules0000755000175000017500000000106612653206625013652 0ustar matsmats#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 # Use hardening options to build the package export DEB_BUILD_MAINT_OPTIONS = hardening=+all # Use Qt 5 export QT_SELECT=5 %: dh $@ pumpa-0.9.2/debian/copyright0000644000175000017500000005745012653206625014535 0ustar matsmatsFormat: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: pumpa Source: https://pumpa.branchable.com/ Files: * Copyright: 2013-2015, Mats Sjöberg License: GPL-3.0+ Files: src/kQOAuth/* Copyright: 2012-2013, Johan Paul License: LGPL-2.1+ Files: images/pumpa* Copyright: Joshua Taylor License: CC-BY-SA-3.0 or GPL-3.0+ Files: src/sundown/* Copyright: 2008, Natacha Porté 2011, Vicent Marti License: MIT2 Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. . THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. License: GPL-3.0+ 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 3 of the License, or (at your option) any later version. . This package 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, see . . On Debian systems, the complete text of the GNU General Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". License: LGPL-2.1+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. . This package 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 Lesser General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . . On Debian systems, the complete text of the GNU Lesser General Public License can be found in "/usr/share/common-licenses/LGPL-2.1". License: CC-BY-SA-3.0 Creative Commons Attribution-ShareAlike 3.0 Unported . CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. . License . THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. . BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. . 1. Definitions . a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. . b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. . c. "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. . d. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. . e. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. . f. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. . g. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. . h. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. . i. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. . j. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. . k. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. . 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. . 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: . a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; . b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; . c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, . d. to Distribute and Publicly Perform Adaptations. . e. For the avoidance of doubt: . i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; . ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, . iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. . The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. . 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: . a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. . b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. . c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. . d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. . 5. Representations, Warranties and Disclaimer . UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. . 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. . 7. Termination . a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. . b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. . 8. Miscellaneous . a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. . b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. . c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. . d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. . e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. . f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. . Creative Commons Notice . Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. . Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. . Creative Commons may be contacted at http://creativecommons.org/. pumpa-0.9.2/debian/changelog0000644000175000017500000000513412653206625014444 0ustar matsmatspumpa (0.9.2-1) unstable; urgency=low * New feature: user profile and avatar image editing. * Shares are now visualised with a recycling icon. * New language: Finnish. * Fixed bug where a shared object might be seen as already liked. * Fixed bug where certain authors were seen as being followed even though they weren't. -- Mats Sjöberg Sat, 30 Jan 2016 15:21:25 +0200 pumpa (0.9.1-1) unstable; urgency=low * Auto-link urls only if we are not using Markdown-mode. * Now has an optional character count for people cross-posting to character limited instances. * Multiple context tabs can now be opened. * Now compiles against Qt 5 by default. (Closes: #770145, #771457 * Added Russian translation by Nikolay Merinov. * Now presents an option to abort or ignore when confronted with an SSL cert error. (Closes: #781352) -- Mats Sjöberg Sun, 17 May 2015 20:23:37 +0300 pumpa (0.9-1) unstable; urgency=low * Fixed UTF-8 bug introduced in v0.8.4. * Fixed bug where image upload consistently fails after an aborted upload. (Closes: #734477) * Complete @-tagging with display name instead of nick name. * More aggressive about reloading inbox to better get missing replies. (Closes: #737081) * Fixed download manager memory leaks. * Support for editing posts (in HTML only). * Fixed bug where copy by Ctrl-C didn't work in text widgets. * Be better about showing comments in temporal order. * Better display of deleted posts. * Various minor bug fixes. -- Mats Sjöberg Sun, 24 Aug 2014 16:40:19 +0300 pumpa (0.8.4-1) unstable; urgency=low * Avatar-menu with new auto-minimise feature that automatically collapses posts of e.g. noisy users. * To/Cc now editable on replies. * Inline HTML now allowed using libtidy. * Updated German, Spanish, Italian and Na'vi translations. * Various bug fixes -- Mats Sjöberg Sun, 08 Jun 2014 18:27:01 +0300 pumpa (0.8.3-1) unstable; urgency=low * Added German translation. * Preliminary support for animated GIFs. (Closes: #734494) * Fixed bug where replies in context view would only go to original author. * Validate SSL certificate on every connection. Don't fall back to non-SSL. * Ability to minimise posts in stream. * Improvements to fetching replies from people not being followed. * Various minor bug fixes. -- Mats Sjöberg Sun, 23 Mar 2014 20:37:45 +0200 pumpa (0.8.2-1) unstable; urgency=low * Initial release. (Closes: #732653) -- Mats Sjöberg Sun, 29 Dec 2013 11:34:27 +0200 pumpa-0.9.2/debian/control0000644000175000017500000000147112653206625014175 0ustar matsmatsSource: pumpa Section: net Priority: optional Maintainer: Mats Sjöberg Build-Depends: debhelper (>= 9), qtbase5-dev, qt5-qmake, libaspell-dev, libtidy-dev Standards-Version: 3.9.6 Homepage: https://pumpa.branchable.com/ Vcs-Git: git://pumpa.branchable.com/ Vcs-Browser: http://source.pumpa.branchable.com/?p=source.git;a=summary Package: pumpa Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: simple desktop client for pump.io, the distributed social network Pumpa is a simple Qt-based desktop client for identi.ca and other pump.io-based distributed social network services. Pumpa offers several improvements over using the web interface, such as better notifications and the ability to @reply particular users. pumpa-0.9.2/debian/watch0000644000175000017500000000045012653206625013617 0ustar matsmats# Upstream source code maintained on gitorious at # http://source.pumpa.branchable.com/?p=source.git;a=summary # New releases are tagged with the version number, but there is no way # to determine a URL scheme to watch. Package is maintained by # upstream author so this should not be a problem. pumpa-0.9.2/debian/menu0000644000175000017500000000025612653206625013461 0ustar matsmats?package(pumpa): \ needs="X11" \ section="Applications/Network/Communication" \ title="Pumpa" \ command="/usr/bin/pumpa" \ icon="/usr/share/pixmaps/pumpa.xpm" pumpa-0.9.2/pumpa.pro0000644000175000017500000001707512653206625013223 0ustar matsmats# -*- mode: makefile -*- ###################################################################### # Copyright 2015 Mats Sjöberg # # This file is part of the Pumpa programme. # # Pumpa 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 3 of the License, or # (at your option) any later version. # # Pumpa 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 Pumpa. If not, see . ###################################################################### TEMPLATE = app TARGET = pumpa RESOURCES += pumpa.qrc OBJECTS_DIR = obj QT += core gui network # To enable debug mode, run as: # qmake CONFIG+=debug # or uncomment this: # CONFIG+=debug # To disable libtidy (not recommended), run as: # qmake CONFIG+=notidy # or uncomment this line: # CONFIG += notidy CONFIG(release, debug|release) { message("Release mode on") } else { message("Debug mode on") CONFIG -= release # Enable debugging output for different things # DEFINES += DEBUG_QAS DEFINES += DEBUG_NET # DEFINES += DEBUG_NET_MOAR # DEFINES += DEBUG_NET_EVEN_MOAR DEFINES += DEBUG_MEMORY DEFINES += DEBUG_MARKUP # DEFINES += DEBUG_WIDGETS DEFINES += DEBUG_BUTTONS } unix:!macx { message("Enabling dbus") QT += dbus DEFINES += USE_DBUS } win32 { RC_FILE = win32/pumpa.rc } macx { ICON = $${PWD}/icons/pumpa.icns } # Additions for Qt 4 lessThan(QT_MAJOR_VERSION, 5) { message("Configuring for Qt 4") LIBS += -lqjson } # Additions for Qt 5 greaterThan(QT_MAJOR_VERSION, 4) { message("Configuring for Qt 5") QT += widgets DEFINES += QT5 } notidy { message("Disabling libtidy") DEFINES += NO_TIDY } else { message("Using libtidy") LIBS += -ltidy exists( /usr/include/tidy/tidy.h ) { DEFINES += USE_TIDY_TIDY } else:exists( /usr/include/tidy.h ) { DEFINES += USE_TIDY } else:exists( /usr/local/include/tidy.h ) { DEFINES += USE_TIDY } else:exists( /usr/local/include/tidy/tidy.h ) { DEFINES += USE_TIDY_TIDY } else { error("Unable to find libtidy header files for compiling! Install libtidy-dev (Debian, Ubuntu) or libtidy-devel (Fedora).") } } # Optional spell checking support with libaspell exists( /usr/include/aspell.h )|exists( /usr/local/include/aspell.h ) { message("Using aspell") LIBS += -laspell DEFINES += USE_ASPELL } else { warning("Unable to find libaspell header files for compiling. Install libaspell-dev (Debian, Ubuntu) if you want to enable spell-checking.") } # Optionally use etags exists( /usr/bin/etags ) { message("Using etags") PRE_TARGETDEPS += etags } ###################################################################### # Main sources ###################################################################### INCLUDEPATH += src VPATH += src OBJECT_HEADERS = pumpapp.h qactivitystreams.h aswidget.h \ collectionwidget.h contextwidget.h json.h messagewindow.h \ messageedit.h messagerecipients.h fancyhighlighter.h \ qaspell.h actorwidget.h filedownloader.h richtextlabel.h \ oauthwizard.h tabwidget.h util.h pumpasettingsdialog.h \ pumpasettings.h activitywidget.h objectwidget.h \ shortobjectwidget.h fullobjectwidget.h imagelabel.h \ texttoolbutton.h objectwidgetwithsignals.h objectlistwidget.h \ qasabstractobject.h qasobject.h qasactor.h qasactivity.h \ qasobjectlist.h qasactorlist.h qascollection.h \ qasabstractobjectlist.h editprofiledialog.h OBJECT_SOURCES = $$replace(OBJECT_HEADERS, \\.h, .cpp) OBJECT_ALL = $$OBJECT_HEADERS $$OBJECT_SOURCES SUNDOWN_HEADERS = sundown/markdown.h sundown/html.h sundown/buffer.h SUNDOWN_SOURCES = sundown/autolink.c sundown/buffer.c \ sundown/houdini_href_e.c sundown/houdini_html_e.c \ sundown/html.c sundown/markdown.c sundown/stack.c HEADERS += $$OBJECT_HEADERS $$SUNDOWN_HEADERS pumpa_defines.h SOURCES += main.cpp SOURCES += $$OBJECT_SOURCES $$SUNDOWN_SOURCES ###################################################################### # Translation files ###################################################################### TRANSLATIONS = \ translations/pumpa_es.ts \ translations/pumpa_fr.ts \ translations/pumpa_nvi.ts \ translations/pumpa_en.ts \ translations/pumpa_it.ts \ translations/pumpa_de.ts \ translations/pumpa_ru.ts \ translations/pumpa_fi.ts ###################################################################### # kQOAuth sources ###################################################################### INCLUDEPATH += src/kQOAuth VPATH += src/kQOAuth PUBLIC_HEADERS += kqoauthmanager.h \ kqoauthrequest.h \ kqoauthrequest_1.h \ kqoauthrequest_xauth.h \ kqoauthglobals.h PRIVATE_HEADERS += kqoauthrequest_p.h \ kqoauthmanager_p.h \ kqoauthauthreplyserver.h \ kqoauthauthreplyserver_p.h \ kqoauthutils.h \ kqoauthrequest_xauth_p.h HEADERS += \ $$PUBLIC_HEADERS \ $$PRIVATE_HEADERS SOURCES += \ kqoauthmanager.cpp \ kqoauthrequest.cpp \ kqoauthutils.cpp \ kqoauthauthreplyserver.cpp \ kqoauthrequest_1.cpp \ kqoauthrequest_xauth.cpp ###################################################################### # Install target ###################################################################### unix { isEmpty(PREFIX) { PREFIX = /usr/local } BINDIR = $$PREFIX/bin DATADIR =$$PREFIX/share INSTALLS += target desktop icon16 icon24 icon32 icon64 icon96 \ icon128 icon256 icon512 target.path =$$BINDIR desktop.path = $$DATADIR/applications desktop.files += $${TARGET}.desktop icon16.path = $$DATADIR/icons/hicolor/16x16/apps icon16.files += icons/16x16/$${TARGET}.png icon24.path = $$DATADIR/icons/hicolor/24x24/apps icon24.files += icons/24x24/$${TARGET}.png icon32.path = $$DATADIR/icons/hicolor/32x32/apps icon32.files += icons/32x32/$${TARGET}.png icon64.path = $$DATADIR/icons/hicolor/64x64/apps icon64.files += icons/64x64/$${TARGET}.png icon96.path = $$DATADIR/icons/hicolor/96x96/apps icon96.files += icons/96x96/$${TARGET}.png icon128.path = $$DATADIR/icons/hicolor/128x128/apps icon128.files += icons/128x128/$${TARGET}.png icon256.path = $$DATADIR/icons/hicolor/256x256/apps icon256.files += icons/256x256/$${TARGET}.png icon512.path = $$DATADIR/icons/hicolor/512x512/apps icon512.files += icons/512x512/$${TARGET}.png } # target.path = /usr/bin # INSTALLS += target ###################################################################### # Generate documentation ###################################################################### doc.depends = README doc.commands = markdown README > pumpa.html; cp README ~/dev/web/_includes/README.pumpa QMAKE_EXTRA_TARGETS += doc ###################################################################### # Generate TAGS for GNU Emacs and others ###################################################################### ETAGS_INPUTS = $$join(OBJECT_ALL, " src/", "src/") etags.depends = $$split($$ETAGS_INPUTS) etags.commands = etags -o src/TAGS $$ETAGS_INPUTS QMAKE_EXTRA_TARGETS += etags ###################################################################### # Extra stuff to clean up ###################################################################### QMAKE_CLEAN += $DOC_TARGETS src/TAGS