pumpa-0.8.2/0000755000175000017500000000000012260001323011322 5ustar matsmatspumpa-0.8.2/README0000644000175000017500000002510112260001323012201 0ustar matsmats# Pumpa v0.8.2 Greetings, fellow pumpkins! Pumpa is a simple [pump.io][1] client written in C++ and Qt, and licensed under the GNU GPL 3.0 (or later). Please report any bugs or feature requests to [the bug tracker][13]. If that doesn't work for some reason, you can always contact me via e-mail: . The Pumpa source code includes codes and graphics from other projects: - The [kQOAuth library][2] is copyrighted by [Johan Paul][3] and licensed under LGPL 2.1. - The current (temporary?) Pumpa logo is from the ["Fruit and Veggie Inventory"][4] entry to the [Liberated Pixel Cup][5] by Joshua Taylor. 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. - The [sundown Markdown library][8] is copyrighted by Natacha Porté, Vicent Marti and others, and [permissively licensed][9]. - [Tim Schumacher][16] contributed several improvements and installer code for Microsoft Windows systems. Oh, and "pumpa" is a Swedish word that means either "to pump" (as a verb) or "pumpkin" (as a noun) :-) Pumpa was mentioned on [LWN: "Searching for a pump.io client"](https://lwn.net/Articles/575691/). [1]: http://pump.io/ [2]: https://github.com/kypeli/kQOAuth [3]: http://www.johanpaul.com/ [4]: http://opengameart.org/content/fruit-and-veggie-inventory [5]: http://lpc.opengameart.org/ [8]: https://github.com/vmg/sundown [9]: https://github.com/vmg/sundown#license [13]: https://bugs.saz.im/pumpa ## Installing At this point most people keep up with the git repository and compile Pumpa themselves (for instructions how to do this, see the next two sections). Once Pumpa development stabilises a bit, hopefully packages for different platforms will appear. Below are some packages or installers for different platforms. Note that these are provided by third parties so I have no control over them, please report any problems with them to the respective packagers/authors. ### Fedora By [Matt Molyneaux](https://microca.st/moggers87): ["Metal Biker"](https://microca.st/howcanuhavemyusername) has a Fedora Copr repo for Pumpa and [Dianara][18]: [18]: https://jancoding.wordpress.com/category/projects/dianara/ ### Debian (Spanish) By [Fabián Bonetti](https://identi.ca/mamafree) configured for Spanish: ### openSUSE By ["XRevan86"](https://microca.st/xrevan86): ### Archlinux AUR By ["speps"](https://aur.archlinux.org/packages/?K=speps&SeB=m): ### Microsoft Windows By [Tim Schumacher][16]: [16]: https://fmrl.me/0xAFFE ### Mac OS X By [Al aka chunder@identi.ca](https://identi.ca/chunder): ## Build dependencies Pumpa should build with Qt 4.8 or Qt 5.0 or newer. If you are using Qt 4, you will also need the [QJson library][6]. For example on Debian 7.0 "wheezy" the following command should install everything needed to build the code: aptitude install qt4-qmake libqt4-dev libqjson-dev If you want spell checking install the aspell library as well: aptitude install libaspell-dev On Fedora this should install what you need (courtesy of [Dick Turpin][10]): yum install gcc-c++ qt-devel qt-config qjson-devel [6]: http://qjson.sourceforge.net/ [10]: https://microca.st/dick_turpin/note/toJY8CHTQlqinra0Hr6lFA ## Building To download and build, type the following: git clone git://gitorious.org/pumpa/pumpa.git cd pumpa qmake-qt4 # or just "qmake" on some systems make The procedure on Mac OS X is the same, but getting the dependencies is a bit different, David Haberthür has made a detailed guide for building on Mac OS X here: Axel has a blog post about compiling for Windows here: ## Running On Linux you can start pumpa by running its binary: ./pumpa On Mac OS X a regular clickable application file (pumpa.app) should be created. At first launch an OAuth wizard will pop-up, just enter your pump.io account id, and click Next. Then a the authentication page with your pump.io server will be opened in the web browser, just follow the instructions there. Finally a pair of codes (token, verifier) will appear that you need to copy & paste back into pumpa. ## Markdown When you are posting a new note or comment you can use [Markdown syntax][7], with the exception that inline HTML is **not** allowed. This is because otherwise it would be very easy to add broken HTML which will be very messy... For example: - \*stars\* make the word emphasised, typically *italics* - \*\*double stars\*\* strong emphasis, typically **bold** - surround links with < and > like so: <http://example.com/> - link text \[like this\]\(http://example.com/\) - a hash (#) at the start of the line makes an H1 heading, so be careful with hash tags :-) Enable the Preview below the text editing window to see what the output will be before posting it to pump.io. For the [full Markdown syntax see John Gruber's page][7]. [7]: http://daringfireball.net/projects/markdown/syntax ## Configuration Most features and configuration options should be obvious from the graphical user interface, but some are a bit hidden. For example, while Pumpa doesn't support multiple accounts, you can always start it with a different configuration file (which can specify another pump.io account for example) like this: ./pumpa -c path_to_alternative.conf If you are setting up a new account you can give the path to a non-existent conf-file and Pumpa will run the setup wizard and create the conf-file for you with the name you specified. The location of the default configuration file depends on Qt, which [tries to pick a location that makes sense for your operating system][12]. E.g. in GNU/Linux systems it is typically in: ~/.config/pumpa/pumpa.conf Most configuration options are exposed via the preferences dialog in Pumpa, except for setting the link colour. The link colour is supposed to be automatically set by the theme settings of your desktop environment, but several people requested a way to override this. You can add a line like this under the `[%General]` section in the configuration file: link_color=#333 The text after the equals sign can be [any text string that Qt can parse as a colour][11]. (Yes I know colour is spelt wrong in the config name :-) [11]: http://qt-project.org/doc/qt-4.8/qcolor.html#setNamedColor [12]: http://qt-project.org/doc/qt-4.8/qsettings.html#locations-where-application-settings-are-stored ## Debug mode If you experience crashes it may be useful to run Pumpa in debug mode. Right now you'll need to recompile it for that with the following steps: cd pumpa make clean qmake CONFIG+=debug make To run in the debugger (you need to have `gdb` installed): gdb ./pumpa Inside gdb you then start Pumpa with the command `run`, when it has crashed you'll be thrown back to the debugger, then type `where`, which will tell you where it crashed. This information can be very useful (in particular the first 10-20 lines), please include it if you report a bug. ## Translations Pumpa currently has Spanish, French, [Na'vi][17] and Italian translations. Any new translations are most welcome, but please tell me first () so that people don't do duplicate work! If you want to translate Pumpa you need to edit a .ts file. Any TS translation tool is probably OK, but these instructions assume you will use the [Linguist tool][15] that comes with Qt. Here are the steps: - Download and install Qt development tools, e.g. in Debian you need the `qt4-dev-tools` package. You can also just install the full Qt system from the [Qt Project web site][14] (Linux, Mac and Windows). - Pull the most recent version of Pumpa from git. If you have cloned it earlier (as described above) just do a pull to get the newest version: git pull (alternatively you could just grab the .ts file directly from the [gitorious web site](https://gitorious.org/pumpa/pumpa/trees/master/translations)). - In Pumpa there should be a `translations` directory with several .ts files, called e.g. `pumpa_es.ts` for Spanish, `pumpa_de.ts` for German, and so on. If you cannot find one for your language you can ask me to add it (or add it yourself, it needs a line under `TRANSLATIONS` in `pumpa.pro` and then run `lupdate` on the CLI). - Open the .ts file in [Qt Linguist][15] and start filling in the fields in your language. Feel free to ask any questions about the context of the texts, or if you want to have something improved to better fit your language. - Some menu items and buttons have texts with ampersands, e.g. "&Help", this means that the next character is an ALT-shortcut. So in this case ALT-h would launch the Help menu. It is OK to change the shortcut when translating, but make sure that you do not have the same shortcut for many things :-) - Once you are done you can make your own clone on gitorious and make a pull request, or just email me the .ts file if that's easier. - If you want to try it on Pumpa right away, you need to run the command: lrelease pumpa.pro (You need to use `lrelease-qt4` on some systems, e.g. Fedora.) Pumpa should detect your systems locale setting and use the correct language. If that doesn't work, or your locale is different, you can always force Pumpa to pick the right one, e.g. for Spanish: ./pumpa -l es Or add this under the `[%General]` section in the configuration file: locale=es [14]: https://qt-project.org/downloads [15]: http://qt-project.org/doc/qt-4.8/linguist-translators.html [17]: https://en.wikipedia.org/wiki/Na%27vi_language ## License Copyright 2013 Mats Sjöberg . All of the source code of Pumpa is licensed under the GPLv3. *GPLv3* 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 . pumpa-0.8.2/translations/0000755000175000017500000000000012260001323014043 5ustar matsmatspumpa-0.8.2/translations/pumpa_en.ts0000644000175000017500000010115612260001323016223 0ustar matsmats ActivityWidget via %1 To: CC: Public CollectionWidget Load older FileDownloader Network error: Unable to download %1 (Error #%2). Could not open file %1 for writing: FullObjectWidget delete comment [No description] at %1 unlike like share stop following follow You like this. %1 likes this. %1 like this. and %Ln other person(s) and one other person and %n other persons %Ln persons shared this. one person shared this. %n persons shared this. shared this. %Ln person(s) shared this. Show all %1 replies post note image Are you sure you want to delete this %1? Share this %1 by %2? Are you sure you want to stop following MessageEdit Spelling suggestions... MessageRecipients - MessageWindow Use Markdown [help] To: Cc: &Remove picture + &To + &Cc Title (optional) Cancel Preview Send message Select recipient (To) Select recipient (Cc) Post a note Post a reply Mentions: Select Image Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) Sorry! That file didn't appear to be an image. &Add picture &Change picture 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> 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 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 Public You have %1 new notifications. %1 commented: %1 wrote: 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! 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 Notifications Never Direct only Direct or mention Direct, mention or inbox Anything Highlight tray icon on: Popup notification on: Currently logged in as %1. QObject a few seconds ago one minute ago %n minute(s) ago %n minute ago %n minutes ago %n hour(s) ago %n hour ago %n hours ago pumpa-0.8.2/translations/pumpa_nvi.ts0000644000175000017500000011300512260001323016411 0ustar matsmats ActivityWidget via %1 ìlä %1 To: Ne: CC: Kop: Public Frapo CollectionWidget Load older Run 'upxareti aham FileDownloader Network error: Tìngäzìk apuslltxe: Unable to download %1 (Error #%2). Ke tsun mivunge %1it (Tìngäzìk #%2). Could not open file %1 for writing: Pamrelìri ke tsun sivar %1it: FullObjectWidget delete 'aku comment säplltxevi [No description] [Kea tìsla'tsu] at %1 at ro %1 unlike ke sunu like sunu share käsrin stop following ke 'eylan si follow eylan si You like this. and %Ln other person(s) %Ln person(s) shared this. likes this. marcó «me gusta». %1 like this. like this. fì'u sunu %1ur. %1 likes this. fì'u serunu %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 note image Are you sure you want to delete this %1? Srake nga new 'ivaku %1it? Share this %1 by %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 + &Cc Picture title (optional) Relyä tstxo (ke kamin) Use Markdown [help] Title (optional) Cancel Ftang Preview Send message Fpe' 'upxareti Select recipient (To) Select recipient (Cc) Post a note Ngop 'upxareti Post a reply Ngop säplltxeviti Mentions: 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 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> 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.) Token: Tokìn: Verifier: Verìfer: 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 Fi&rehose &Paypìpe &Followers &Nga sunu Faysuteru F&ollowing &Eylan You have new messages. 'upxare amip lu ngaru. 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 &About &Pumpateri About &Qt &Qtteri New &Note &Ngop 'upxareti Ctrl+N Ctrl+N Ctrl+D Followers Public Frapo You have %1 new notifications. %1 commented: %1 wrote: Firehose Following Favorites 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 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 %1teri 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 A&ctivities Loading ... Nerìn ... Ready! Tam! Unable to post message! Ke tsun fpe' 'upxareti! Network or authorisation error [%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 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. QObject a few seconds ago set one minute ago 'aw mìnut kam %n minute(s) ago %n hour(s) ago %1 minutes ago %1a mìnut kam 1 hour ago 'awa hour kam %1 hours ago %1a ayhour kam pumpa-0.8.2/translations/pumpa_es.ts0000644000175000017500000011223312260001323016226 0ustar matsmats ActivityWidget via %1 vía %1 To: Para: CC: CC: Public Público CollectionWidget Load older Cargar antiguos FileDownloader Network error: Error de red: 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 comment comentar [No description] [Sin descripción] at %1 at en %1 unlike no me gusta like me gusta share compartir stop following dejar de seguir a follow seguir a 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. 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: 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 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> 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. 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 &About &Acerca de About &Qt Acerca de &Qt New &Note Nueva &Nota Ctrl+N Ctrl+N Ctrl+D Ctrl+D Followers Seguidores Public Público You have %1 new notifications. Tienes %1 notificaciones nuevas. %1 commented: %1 comentó: %1 wrote: %1 escribió: 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 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! 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 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. QObject a few seconds ago hace unos segundos one minute ago hace un minuto %n minute(s) ago hace %n minuto. hace %n minutos. %n hour(s) ago hace %n hora hace %n horas pumpa-0.8.2/translations/pumpa_en.qm0000644000175000017500000000061612260001323016211 0ustar matsmatsVotre 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.8.2/translations/pumpa_nvi.qm0000644000175000017500000002472612260001323016413 0ustar matsmats2S'4q6"5:*u x&  V#%=]I/]{f{{$|I{4   OaY (R~Vѥ b<q,Ql{3g;SxW``l02HN#Z@~Ae?^%!  +"e lf  n \ 1% ľ!t  .m!- TGw d<4 eTz @  Pu ȗ" 4 F( G I :N% Zc e N^  ! # /y. }0 n o% %I $ |1 % j$ %8UqM MnŔ i&5 l %1 via %1ActivityWidgetKop:CC:ActivityWidget FrapoPublicActivityWidgetNe:To:ActivityWidget$Run 'upxareti aham Load olderCollectionWidget:Pamrelri ke tsun sivar %1it:$Could not open file %1 for writing: FileDownloader(Tngzk apuslltxe: Network error: FileDownloaderHKe tsun mivunge %1it (Tngzk #%2)."Unable to download %1 (Error #%2).FileDownloaderf'u sunu %1ur. %1 like this.FullObjectWidget"f'u serunu %1ur.%1 likes this.FullObjectWidget4Srake nga new 'ivaku %1it?(Are you sure you want to delete this %1?FullObjectWidget8Srake nga ke new 'eylan sivi'Are you sure you want to stop followingFullObjectWidget(Wntxu 'upxareti a%1Show all %1 repliesFullObjectWidget[Kea tsla'tsu][No description]FullObjectWidget ro %1at %1FullObjectWidgetsplltxevicommentFullObjectWidget'akudeleteFullObjectWidgeteylan sifollowFullObjectWidgetsunulikeFullObjectWidget ksrinshareFullObjectWidget kamsrin f'uti. shared this.FullObjectWidgetke 'eylan sistop followingFullObjectWidgetke sunuunlikeFullObjectWidget&Sung relit &Add picture MessageWindow&Latem relit&Change picture MessageWindow&'aku relit&Remove picture MessageWindow FtangCancel MessageWindowKop:Cc: MessageWindowXayRel (*.png *.jpg *.jpeg *.gif);; ayu (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) MessageWindowNgop 'upxareti Post a note MessageWindow"Ngop splltxeviti Post a reply MessageWindowFtxey relit Select Image MessageWindowFpe' 'upxareti Send message MessageWindowNgaytxoa!Sorry! MessageWindow Tsa'u ke lu rel.'That file didn't appear to be an image. MessageWindowNe:To: MessageWindowF<b>Pump.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.Ziva'u nprrte pumparu!Welcome to Pumpa!OAuthFirstPageTung pumpatiAuthorise PumpaOAuthSecondPage Tokn:Token:OAuthSecondPageVerfer: 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 &Wntow %1 &WindowPumpApp&Pumpateri&AboutPumpApp&Upxare&ContextPumpApp &Oeri&DirectPumpApp&&Nga sunu Faysuteru &FollowersPumpApp &Srung&HelpPumpApp&Payfya&InboxPumpApp&Oeteri &MentionsPumpApp &Pumpa&PumpaPumpApp&Nn payfya&Reload timelinePumpApp<Pumpri pumpa fkeytok fte sar.!A simple Qt-based pump.io client.PumpApp %1teriAbout %1PumpApp&Qtteri About &QtPumpApp 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&'eylan siF&ollow an accountPumpApp &Eylan F&ollowingPumpApp&Payppe Fi&rehosePumpApp 'eylan si tuteruFollow pump.io userPumpAppWanHidePumpAppLTute ke lu eyawr (ke steftxaw pumpit):"Invalid user (cannot check site): PumpAppTute lu eyawr:Invalid user: PumpApp Nn 'upxare ahamLoad older in timelinePumpAppNern ... Loading ...PumpAppLef&krr Mean&whilePumpApp&Ngop 'upxareti New &NotePumpAppOey ayTlatem PreferencesPumpApp FrapoPublicPumpAppTam!Ready!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.8.2/translations/pumpa_fr.ts0000644000175000017500000011457012260001323016234 0ustar matsmats ActivityWidget via %1 via %1 To: À: CC: CC: Public Public CollectionWidget Load older Plus anciens FileDownloader Network error: Erreur de connexion: 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 comment commenter [No description] [Pas de déscription] at %1 à %1 unlike ne plus aimer like aimer share partager stop following arrêter de suivre follow suivre You like this. and %Ln other person(s) %Ln person(s) shared this. %1 like this. aime ou aiment ? comment faire la disctinction entre likes->onlyYou() et nl > 1 ? %1 aime. %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 post note image 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 Preview Send message Envoyer le message Select recipient (To) Select recipient (Cc) Post a note Écrire une note Post a reply Écrire une réponse Mentions: 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 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> 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 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 &About &A propos About &Qt À propos de &Qt New &Note Nouvelle &Note Ctrl+N Ctrl+N Ctrl+D Followers Abonnés Public Public You have %1 new notifications. %1 commented: %1 wrote: 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! 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 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. QObject a few seconds ago il y a quelques secondes one minute ago il y a une minute %n minute(s) ago %n hour(s) 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.8.2/translations/pumpa_it.qm0000644000175000017500000005014212260001323016222 0ustar matsmats<;*0 +!-* $-+ UG6TGhbo!p7y _E8\es3us4=ե;H No{jF4sx :1[F=\Ď*Ih a!Z2S'74q v686Fq6'9$ai:u&#CV#I] .I 5525`555{b{<{HI7d{<W   @aYHR8]Vѥ b< q,l3{3gKr_;S6CFr> x?+``ϗcn@l@~0 U2HNGZ@9~Ae !)(^K? @ +"e<, 0pA lf3  ; n 1Y ľEd ? .mE TG d<4 eT no  C Pu ȗGU 4 F(AG \mJ G I :N2 ^" X XY3 Zc!# e6 *D[ =*D NQ Mn)Ŕ>iK via %1 via %1ActivityWidgetCC:CC:ActivityWidgetPubblicoPublicActivityWidgetA:To:ActivityWidget&Carica meno recenti Load olderCollectionWidgetZNon 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?FullObjectWidgetNSei sicuro di voler smettere di seguire'Are you sure you want to stop followingFullObjectWidget6Condividi questo %1 via %2?Share this %1 by %2?FullObjectWidget6Mostra tutte le %1 risposteShow all %1 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 %1at %1FullObjectWidgetcommentacommentFullObjectWidgeteliminadeleteFullObjectWidget seguifollowFullObjectWidgetimmagineimageFullObjectWidgetmi piacelikeFullObjectWidgetnotanoteFullObjectWidgetmessaggiopostFullObjectWidgetcondividishareFullObjectWidget4condiviso questo elemento. shared this.FullObjectWidget"smetti di seguirestop followingFullObjectWidgetnon mi piaceunlikeFullObjectWidget6Suggerimenti ortografici...Spelling suggestions... MessageEdit--MessageRecipients$A&ggiungi immagine &Add picture MessageWindow Ca&mbia immagine&Change picture MessageWindow"&Rimuovi immagine&Remove picture MessageWindow + &Cc+ &Cc MessageWindow+ &A+ &To MessageWindowCancellaCancel MessageWindowCc:Cc: MessageWindow|Files immagine (*.png *.jpg *.jpeg *.gif);;Tutti i files (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) MessageWindowMenzioni: Mentions: MessageWindow"Pubblica una nota Post a note MessageWindow*Pubblica una risposta Post a reply MessageWindowAnteprimaPreview MessageWindow*Seleziona un'immagine Select Image MessageWindow6Seleziona destinatario (Cc)Select recipient (Cc) MessageWindow4Seleziona destinatario (A)Select recipient (To) MessageWindowInvia messaggio Send message 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 AvantiNextOAuthFirstPage&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 &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&TabsPumpApp<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 ActivitiesPumpAppNCopyright &copy; 2013 Mats Sj&ouml;berg'Copyright © 2013 Mats SjöbergPumpApp Ctrl+DCtrl+DPumpApp Ctrl+LCtrl+LPumpApp Ctrl+NCtrl+NPumpApp Ctrl+OCtrl+OPumpApp Ctrl+QCtrl+QPumpApp Ctrl+RCtrl+RPumpApp E&sciE&xitPumpApprInserisci il webfinger ID della persona che vuoi seguire:(Enter webfinger ID of person to follow: PumpAppErrore:Error: 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: PumpAppRCarica elementi pi vecchi nella timelineLoad older in timelinePumpAppCaricamento ... Loading ...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!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.Hai %1 nuove notifiche.You have %1 new notifications.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 trayPumpaSettingsDialog%n ora fa%n ore fa%n hour(s) agoQObject%n minuto fa%n minuti fa%n minute(s) agoQObject pochi secondi faa few seconds agoQObjectun minuto faone minute agoQObjectpumpa-0.8.2/translations/pumpa_es.qm0000644000175000017500000005054212260001323016221 0ustar matsmats0VVvq?V<'*0 +"-* -+ G7TTHXhb8o_p7y E9es4s5sե\Ď+Ih!a!2S'84q 6:#6GM6':\ai2:u&DV#J] I ,6@6n6667&{d{={II8{= v  AaYR9]Vѥb< q,l54{^3gr_8;S7Fr> Fx@M``ϗ^cnABlA0 2HNHZ@:~Ae *!)^L;T BM +"e=D 0p{ lf4  < n 1 ľF\ @ .mF TG d<4l eT 3 n? ] i Pu y ȗH 4 F(B \mK{ G? I :N4* ^"E Xb XY Zc!O e8' *EQ =*E N = F I /y }0 I Xk iFC9 mC8V n>L o%! %9S J |1 K jIR%8U;qMD+4 _KMn*Ŕ?iL va %1 via %1ActivityWidgetCC:CC:ActivityWidgetPblicoPublicActivityWidget Para:To:ActivityWidgetCargar antiguos Load olderCollectionWidget^No se pudo abrir el fichero %1 para escritura: $Could not open file %1 for writing: FileDownloaderError de red:Network error: 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?FullObjectWidget^Ests seguro de que quieres dejar de seguirle?'Are you sure you want to stop followingFullObjectWidget(Compartir %1 de %2?Share this %1 by %2?FullObjectWidget>Mostrar todas las %1 respuestasShow all %1 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 %1at %1FullObjectWidgetcomentarcommentFullObjectWidget borrardeleteFullObjectWidgetseguir afollowFullObjectWidgetla imagenimageFullObjectWidgetme gustalikeFullObjectWidgetla notanoteFullObjectWidgetla publicacinpostFullObjectWidgetcompartirshareFullObjectWidget8comparti/compartieron esto. shared this.FullObjectWidget"dejar de seguir astop followingFullObjectWidgetno me gustaunlikeFullObjectWidget8Sugerencias de ortografa...Spelling suggestions... MessageEdit--MessageRecipients&Agregar imagen &Add picture MessageWindow&Cambiar imagen&Change picture MessageWindowBorra&r imagen&Remove picture MessageWindow + &Cc+ &Cc MessageWindow+ &Para+ &To MessageWindowCancelarCancel MessageWindowCc:Cc: MessageWindowArchivos de imagen (*.png *.jpg *.jpeg *.gif);;Todos los archivos (*.*)7Image files (*.png *.jpg *.jpeg *.gif);;All files (*.*) MessageWindowMenciones: Mentions: MessageWindow"Publicar una nota Post a note MessageWindow,Publicar una respuesta Post a reply MessageWindowPrevisualizarPreview MessageWindow$Seleccionar imagen Select Image MessageWindow8Selecciona destinatario (Cc)Select recipient (Cc) MessageWindow<Selecciona destinatario (Para)Select recipient (To) MessageWindowEnviar mensaje Send message 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.

OAuthFirstPageSiguienteNextOAuthFirstPage(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 ObjectWidget%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&TabsPumpApp<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 ActivitiesPumpAppNCopyright &copy; 2013 Mats Sj&ouml;berg'Copyright © 2013 Mats SjöbergPumpApp Ctrl+DCtrl+DPumpApp Ctrl+LCtrl+LPumpApp Ctrl+NCtrl+NPumpApp Ctrl+OCtrl+OPumpApp Ctrl+QCtrl+QPumpApp Ctrl+RCtrl+RPumpApp &SalirE&xitPumpAppbIntroduce el webfinger ID de la persona a seguir:(Enter webfinger ID of person to follow: PumpApp Error:Error: 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: PumpAppBCargar antiguos en lnea temporalLoad older in timelinePumpAppCargando ... Loading ...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!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!PumpApp@Tienes %1 notificaciones nuevas.You have %1 new notifications.PumpApp 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:PumpaSettingsDialogPblicoPublicPumpaSettingsDialogPIntervalo de actualizacin (en minutos):Update interval (in minutes):PumpaSettingsDialogFUsar icono en la bandeja de sistemaUse icon in system trayPumpaSettingsDialoghace %n horahace %n horas%n hour(s) agoQObjecthace %n minuto. hace %n minutos.%n minute(s) agoQObject$hace unos segundosa few seconds agoQObjecthace un minutoone minute agoQObjectpumpa-0.8.2/translations/pumpa_it.ts0000644000175000017500000010767512260001323016251 0ustar matsmats ActivityWidget via %1 via %1 To: A: CC: CC: Public Pubblico CollectionWidget Load older Carica meno recenti FileDownloader Network error: Errore di rete: 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 comment commenta [No description] [Nessuna descrizione] at %1 in %1 unlike non mi piace like mi piace share condividi stop following smetti di seguire follow segui 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 %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: 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 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> 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 Public Pubblico You have %1 new notifications. Hai %1 nuove notifiche. %1 commented: %1 ha commentato: %1 wrote: %1 ha scritto: 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 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>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! 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 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. QObject a few seconds ago pochi secondi fa one minute ago un minuto fa %n minute(s) ago %n minuto fa %n minuti fa %n hour(s) ago %n ora fa %n ore fa pumpa-0.8.2/pumpa.pro0000644000175000017500000001362412260001323013174 0ustar matsmats# -*- mode: makefile -*- ###################################################################### # Copyright 2013 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 # 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_MEMORY DEFINES += DEBUG_MARKUP # DEFINES += DEBUG_WIDGETS } unix:!macx { message("Enabling dbus") QT += dbus DEFINES += USE_DBUS } win32 { RC_FILE = win32/pumpa.rc } # 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 } # Optional spell checking support with libaspell exists( /usr/include/aspell.h ) { message("Using aspell") LIBS += -laspell DEFINES += USE_ASPELL } # 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 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 ###################################################################### # 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 # } # BINDIR = $$PREFIX/bin # DATADIR =$$PREFIX/share # #DEFINES += DATADIR=\\\"$$DATADIR\\\" PKGDATADIR=\\\"$$PKGDATADIR\\\" # #MAKE INSTALL # INSTALLS += target desktop icon32 # target.path =$$BINDIR # desktop.path = $$DATADIR/applications # desktop.files += $${TARGET}.desktop # icon32.path = $$DATADIR/icons/hicolor/32x32/apps # icon32.files += images/$${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 pumpa-0.8.2/images/0000755000175000017500000000000012260001323012567 5ustar matsmatspumpa-0.8.2/images/loader.gif0000644000175000017500000000347112260001323014531 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.8.2/images/pumpa.xpm0000644000175000017500000000261512260001323014443 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.8.2/images/pumpa.ico0000644000175000017500000000121712260001323014406 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 M6kLK7YJM} h&V Ron١#6aLl Sz8PE*9Afy0*X7^]|O>~/ [N``ܻLwF@;2u~imo7{@%b!k+&N:q<zIENDB`pumpa-0.8.2/images/empty.gif0000644000175000017500000000011412260001323014410 0ustar matsmatsGIF89a!Created with GIMP! ,ڋ>;pumpa-0.8.2/images/pumpa_glow.png0000644000175000017500000000202612260001323015447 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.8.2/images/broken_image.png0000644000175000017500000000124612260001323015722 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.8.2/images/default.png0000644000175000017500000000233612260001323014725 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.7 Pumpa Mats Sjöberg, Tim Schumacher Pumpa true @rootDir@InstallationDirectory pumpa-0.8.2/win32/pumpa.rc0000644000175000017500000000005412260001323013733 0ustar matsmatsID_ICON ICON DISCARDABLE "images/pumpa.ico" pumpa-0.8.2/win32/packages/0000755000175000017500000000000012260001323014042 5ustar matsmatspumpa-0.8.2/win32/packages/pumpa/0000755000175000017500000000000012260001323015164 5ustar matsmatspumpa-0.8.2/win32/packages/pumpa/meta/0000755000175000017500000000000012260001323016112 5ustar matsmatspumpa-0.8.2/win32/packages/pumpa/meta/userscript.qs0000644000175000017500000000111312260001323020656 0ustar matsmatsfunction Component() { var programFiles = installer.environmentVariable("ProgramFiles"); if (programFiles != "") installer.setValue("TargetDir", programFiles + "/pumpa"); } Component.prototype.isDefault = function() { // select the component by default return true; } Component.prototype.createOperations = function() { // call default implementation to actually install README.txt! component.createOperations(); if (installer.value("os") === "win") { component.addOperation("CreateShortcut", "@TargetDir@/pumpa.exe", "@StartMenuDir@/pumpa.lnk"); } }pumpa-0.8.2/win32/packages/pumpa/meta/package.xml0000644000175000017500000000103512260001323020226 0ustar matsmats Pumpa Pumpa Main Components 0.6-1 2013-07-26 pumpa script pumpa-0.8.2/pumpa.qrc0000644000175000017500000000067612260001323013164 0ustar matsmats images/pumpa.png images/pumpa_glow.png images/default.png images/broken_image.png images/loader.gif images/empty.gif translations/pumpa_en.qm translations/pumpa_es.qm translations/pumpa_fr.qm translations/pumpa_nvi.qm translations/pumpa_it.qm pumpa-0.8.2/LICENSE0000644000175000017500000010451312260001323012333 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.8.2/pumpa.desktop0000644000175000017500000000031512260001323014036 0ustar matsmats[Desktop Entry] Version=1.0 Name=Pumpa Exec=/usr/bin/pumpa Icon=/usr/share/icons/hicolor/32x32/apps/pumpa.png Terminal=false Type=Application Categories=Network; Keywords=pump.io;social network;microblog; pumpa-0.8.2/docs/0000755000175000017500000000000012260001323012252 5ustar matsmatspumpa-0.8.2/docs/pumpa.10000644000175000017500000000255512260001323013465 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.8.2/debian/0000755000175000017500000000000012260001323012544 5ustar matsmatspumpa-0.8.2/debian/compat0000644000175000017500000000000212260001323013742 0ustar matsmats9 pumpa-0.8.2/debian/install0000644000175000017500000000021712260001323014135 0ustar matsmatspumpa.desktop usr/share/applications images/pumpa.png usr/share/icons/hicolor/32x32/apps/ images/pumpa.xpm usr/share/icons/hicolor/32x32/apps/ pumpa-0.8.2/debian/menu0000644000175000017500000000027712260001323013441 0ustar matsmats?package(pumpa): \ needs="X11" \ section="Applications/Network/Communication" \ title="Pumpa" \ command="/usr/bin/pumpa" \ icon="/usr/share/icons/hicolor/32x32/apps/pumpa.xpm" pumpa-0.8.2/debian/copyright0000644000175000017500000005744712260001323014520 0ustar matsmatsFormat: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: pumpa Source: http://saz.im/software/pumpa.html Files: * Copyright: 2013, 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.8.2/debian/source/0000755000175000017500000000000012260001323014044 5ustar matsmatspumpa-0.8.2/debian/source/include-binaries0000644000175000017500000000017612260001323017210 0ustar matsmatstranslations/pumpa_en.qm translations/pumpa_es.qm translations/pumpa_fr.qm translations/pumpa_it.qm translations/pumpa_nvi.qm pumpa-0.8.2/debian/source/format0000644000175000017500000000001412260001323015252 0ustar matsmats3.0 (quilt) pumpa-0.8.2/debian/control0000644000175000017500000000146012260001323014150 0ustar matsmatsSource: pumpa Section: net Priority: optional Maintainer: Mats Sjöberg Build-Depends: debhelper (>= 9), libqt4-dev, qt4-qmake, libqjson-dev, libaspell-dev Standards-Version: 3.9.5 Homepage: http://saz.im/software/pumpa.html Vcs-Git: git://git@gitorious.org:pumpa/pumpa.git Vcs-Browser: https://gitorious.org/pumpa/pumpa 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.8.2/debian/docs0000644000175000017500000000000712260001323013414 0ustar matsmatsREADME pumpa-0.8.2/debian/pumpa.manpages0000644000175000017500000000001512260001323015377 0ustar matsmatsdocs/pumpa.1 pumpa-0.8.2/debian/watch0000644000175000017500000000042112260001323013572 0ustar matsmats# Upstream source code maintained on gitorious at # https://gitorious.org/pumpa/pumpa/ # 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.8.2/debian/changelog0000644000175000017500000000022412260001323014414 0ustar matsmatspumpa (0.8.2-1) unstable; urgency=low * Initial release. (Closes: #732653) -- Mats Sjöberg Sun, 29 Dec 2013 11:34:27 +0200 pumpa-0.8.2/debian/rules0000755000175000017500000000102712260001323013624 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 %: dh $@ pumpa-0.8.2/src/0000755000175000017500000000000012260001323012111 5ustar matsmatspumpa-0.8.2/src/qasabstractobject.h0000644000175000017500000000472212260001323015766 0ustar matsmats/* Copyright 2013 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; } 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; }; #endif /* _QASABSTRACTOBJECT_H_ */ pumpa-0.8.2/src/messageedit.h0000644000175000017500000000362412260001323014561 0ustar matsmats/* Copyright 2013 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.8.2/src/activitywidget.cpp0000644000175000017500000001312412260001323015656 0ustar matsmats/* Copyright 2013 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, 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&))); // if (!obj->content().isEmpty() || !obj->displayName().isEmpty() // || (objType == "image" && !obj->imageUrl().isEmpty())) // m_objectWidget = makeObjectWidgetAndConnect(obj, !fullObject); m_objectWidget = new ObjectWidget(NULL, this); ObjectWidgetWithSignals::connectSignals(m_objectWidget, this, true); connect(m_objectWidget, SIGNAL(newReply(QASObject*, QASObjectList*, QASObjectList*)), this, SLOT(onReply(QASObject*, QASObjectList*, QASObjectList*))); 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); // if (m_objectWidget) layout->addWidget(m_objectWidget, 0, Qt::AlignTop); layout->addWidget(new QLabel("


")); changeObject(a); // QASActor* actor = m_activity->actor(); // if (QASActivity::isLikeVerb(verb) && actor && !actor->isYou()) // refreshObject(obj); setLayout(layout); } //------------------------------------------------------------------------------ ActivityWidget::~ActivityWidget() { #ifdef DEBUG_WIDGETS qDebug() << "Deleting ActivityWidget" << m_activity->id(); #endif } //------------------------------------------------------------------------------ void ActivityWidget::changeObject(QASAbstractObject* aObj) { m_activity = qobject_cast(aObj); if (!m_activity) return; const QString verb = m_activity->verb(); QASObject* obj = m_activity->object(); QString objType = obj->type(); bool fullObject = (verb == "post"); m_objectWidget->changeObject(obj, fullObject); bool objectVisible = !obj->content().isEmpty() || !obj->displayName().isEmpty() || (objType == "image" && !obj->imageUrl().isEmpty()); m_objectWidget->setVisible(objectVisible); updateText(); } //------------------------------------------------------------------------------ void ActivityWidget::onObjectChanged() { updateText(); } //------------------------------------------------------------------------------ void ActivityWidget::onReply(QASObject* obj, QASObjectList*, QASObjectList*) { QASObjectList* to = m_activity ? m_activity->to() : NULL; QASObjectList* cc = m_activity ? m_activity->cc() : NULL; emit newReply(obj, to, cc); } //------------------------------------------------------------------------------ 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 == "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(", "); } //------------------------------------------------------------------------------ // ObjectWidget* ActivityWidget::makeObjectWidgetAndConnect(QASObject* obj) { // ObjectWidget* ow = new ObjectWidget(obj, this); // ObjectWidgetWithSignals::connectSignals(ow, this); // connect(ow, SIGNAL(showContext(QASObject*)), // this, SIGNAL(showContext(QASObject*))); // // connect(obj, SIGNAL(changed()), this, SLOT(onObjectChanged()), // // Qt::UniqueConnection); // return ow; // } pumpa-0.8.2/src/qasobjectlist.h0000644000175000017500000000356712260001323015144 0ustar matsmats/* Copyright 2013 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); 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)); } 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.8.2/src/qactivitystreams.cpp0000644000175000017500000000227112260001323016233 0ustar matsmats/* Copyright 2013 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.8.2/src/qasabstractobjectlist.cpp0000644000175000017500000001201712260001323017211 0ustar matsmats/* Copyright 2013 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) { #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 (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 (!older || m_firstTime) { // updateVar doesn't touch it if it is empty in the json updateVar(json, m_prevLink, "links", "prev", "href", dummy); } // We assume that collections come in as newest first, so we add // items starting from the top going downwards. Or if older=true // starting from the end and going downwards (appending). // 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=0; iapiLink(); #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.8.2/src/qaspell.h0000644000175000017500000000264312260001323013730 0ustar matsmats/* Copyright 2013 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.8.2/src/qasobjectlist.cpp0000644000175000017500000000645112260001323015472 0ustar matsmats/* Copyright 2013 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) { 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; i. */ #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.8.2/src/qasobject.h0000644000175000017500000001031212260001323014232 0ustar matsmats/* Copyright 2013 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 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=false); QASActor* asActor(); qint64 sortInt() const { return sortIntByDateTime(m_updated); } QString id() const { return m_id; } QString content() const { return m_content; } 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; } QASLocation* location() const { return m_location; } 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(); } QString excerpt() const; protected: 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; 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; static QMap s_objects; }; typedef QList RecipientList; #endif /* _QASOBJECT_H_ */ pumpa-0.8.2/src/fancyhighlighter.cpp0000644000175000017500000000341212260001323016134 0ustar matsmats/* Copyright 2013 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::SpellCheckUnderline); QRegExp rxa("(^|\\s)([\\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.8.2/src/tabwidget.h0000644000175000017500000000242112260001323014233 0ustar matsmats/* Copyright 2013 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 class TabWidget : public QTabWidget { Q_OBJECT public: TabWidget(QWidget* parent=0); int addTab(QWidget* page, const QString& label, bool highlight=true, bool closable=false); public slots: void highlightTab(int index=-1); void deHighlightTab(int index=-1); protected slots: void closeTab(int index); protected: void addHighlightConnection(QWidget* page, int index); QSignalMapper* sMap; QSet okToClose; }; #endif pumpa-0.8.2/src/main.cpp0000644000175000017500000000731412260001323013546 0ustar matsmats/* Copyright 2013 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"; 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(settingsFile); 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.8.2/src/richtextlabel.h0000644000175000017500000000226112260001323015115 0ustar matsmats/* Copyright 2013 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.8.2/src/sundown/0000755000175000017500000000000012260001323013606 5ustar matsmatspumpa-0.8.2/src/sundown/markdown.h0000644000175000017500000001136512260001323015607 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.8.2/src/sundown/html_blocks.h0000644000175000017500000001532212260001323016263 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.8.2/src/sundown/houdini_html_e.c0000644000175000017500000000377512260001323016755 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.8.2/src/sundown/markdown.c0000644000175000017500000016345512260001323015612 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.8.2/src/sundown/html.c0000755000175000017500000003370212260001323014726 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.8.2/src/sundown/houdini.h0000644000175000017500000000250412260001323015417 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.8.2/src/sundown/autolink.h0000644000175000017500000000264612260001323015615 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.8.2/src/sundown/html.h0000644000175000017500000000367512260001323014736 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.8.2/src/sundown/buffer.h0000644000175000017500000000563112260001323015235 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.8.2/src/sundown/autolink.c0000644000175000017500000001407112260001323015603 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.8.2/src/sundown/Makefile0000644000175000017500000000003012260001323015237 0ustar matsmatsall: $(MAKE) -C ../../ pumpa-0.8.2/src/sundown/html_smartypants.c0000644000175000017500000002514212260001323017367 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.8.2/src/sundown/houdini_href_e.c0000644000175000017500000000560612260001323016730 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.8.2/src/sundown/buffer.c0000644000175000017500000001054612260001323015231 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.8.2/src/sundown/stack.h0000644000175000017500000000064212260001323015066 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.8.2/src/sundown/stack.c0000644000175000017500000000210412260001323015054 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.8.2/src/oauthwizard.h0000644000175000017500000000511412260001323014624 0ustar matsmats/* Copyright 2013 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, 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.8.2/src/util.h0000644000175000017500000000466312260001323013250 0ustar matsmats/* Copyright 2013 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); /* 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 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 addTextMarkup(QString content, bool useMarkdown); //------------------------------------------------------------------------------ #endif /* _UTIL_H_ */ pumpa-0.8.2/src/json.cpp0000644000175000017500000000660412260001323013574 0ustar matsmats/* Copyright 2013 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.8.2/src/shortobjectwidget.cpp0000644000175000017500000000657012260001323016357 0ustar matsmats/* Copyright 2013 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); 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())); updateAvatar(); static QSet expandableTypes; if (expandableTypes.isEmpty()) expandableTypes << "person" << "note" << "comment" << "image" << "video"; m_moreButton->setVisible(expandableTypes.contains(m_object->type())); updateText(); } //------------------------------------------------------------------------------ void ShortObjectWidget::updateAvatar() { QASActor* m_actor = m_object->asActor(); if (!m_actor) m_actor = m_object->author(); m_actorWidget->setActor(m_actor); } //------------------------------------------------------------------------------ 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() { updateAvatar(); updateText(); } //------------------------------------------------------------------------------ pumpa-0.8.2/src/json.h0000644000175000017500000000242712260001323013240 0ustar matsmats/* Copyright 2013 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.8.2/src/pumpapp.h0000644000175000017500000001557012260001323013754 0ustar matsmats/* Copyright 2013 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 #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" //------------------------------------------------------------------------------ class PumpApp : public QMainWindow { Q_OBJECT public: PumpApp(PumpaSettings* settings, QString locale="", QWidget* parent=0); virtual ~PumpApp(); signals: void userAuthorizationStarted(); private slots: void userTestDoneAndFollow(); void trayIconActivated(QSystemTrayIcon::ActivationReason reason); void updateTrayIcon(); void toggleVisible(); void timelineHighlighted(int); void followDialog(); 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 postReply(QASObject* replyToObj, QString content, RecipientList to, RecipientList cc); void follow(QString acctId, bool follow); void onDeleteObject(QASObject* obj); 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 request(QString endpoint, int response_id, KQOAuthRequest::RequestHttpMethod method = KQOAuthRequest::GET, QVariantMap data=QVariantMap()); void exit(); void about(); void preferences(); void newNote(QASObject* obj = NULL, QASObjectList* to = NULL, QASObjectList* cc = NULL); void reload(); void loadOlder(); void startPumping(); void launchOAuthWizard(); void debugAction(); void showFollowers(); void showFollowing(); void showFavourites(); void showUserActivities(); void showFirehose(); 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: bool tabShown(ASWidget* aw) const; 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); void updatePostedImage(QVariantMap obj); void postImageActivity(QVariantMap obj); 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 syncOAuthInfo(); 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* loadOlderAction; QAction* openPrefsAction; QAction* exitAction; QMenu* fileMenu; QAction* aboutAction; QAction* aboutQtAction; QMenu* helpMenu; QAction* m_followersAction; QAction* m_followingAction; QAction* m_favouritesAction; QAction* m_userActivitiesAction; QAction* m_firehoseAction; QMenu* m_tabsMenu; QAction* m_debugAction; KQOAuthManager *oaManager; // KQOAuthRequest *oaRequest; TabWidget* m_tabWidget; CollectionWidget* m_inboxWidget; CollectionWidget* m_directMajorWidget; CollectionWidget* m_directMinorWidget; CollectionWidget* m_inboxMinorWidget; CollectionWidget* m_firehoseWidget; ContextWidget* m_contextWidget; 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; 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; RecipientList m_recipientLists; MessageEdit::completion_t m_completions; QProgressDialog* m_uploadDialog; QNetworkAccessManager* m_nam; QSignalMapper* m_notifyMap; #ifdef USE_DBUS QDBusInterface* m_dbus; #endif }; #endif /* _PUMPAPP_H_ */ pumpa-0.8.2/src/aswidget.h0000644000175000017500000000465412260001323014102 0ustar matsmats/* Copyright 2013 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(); void setEndpoint(QString endpoint, QObject* parent, int asMode=-1); int count() const { return m_object_set.size(); } const QList& newObjects() { return m_newObjects; } signals: void highlightMe(); 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*); 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* aObj); 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.8.2/src/qasabstractobject.cpp0000644000175000017500000001371312260001323016321 0ustar matsmats/* Copyright 2013 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) { bool 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) { return dt.toMSecsSinceEpoch(); } pumpa-0.8.2/src/texttoolbutton.cpp0000644000175000017500000000226712260001323015742 0ustar matsmats/* Copyright 2013 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.8.2/src/qasactorlist.cpp0000644000175000017500000000437412260001323015336 0ustar matsmats/* Copyright 2013 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.8.2/src/shortobjectwidget.h0000644000175000017500000000305712260001323016021 0ustar matsmats/* Copyright 2013 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() {}; signals: void moreClicked(); private slots: void onChanged(); private: void updateText(); void updateAvatar(); TextToolButton* m_moreButton; RichTextLabel* m_textLabel; ActorWidget* m_actorWidget; QASObject* m_object; QASActor* m_actor; }; #endif /* _SHORTOBJECTWIDGET_H_ */ pumpa-0.8.2/src/qascollection.cpp0000644000175000017500000000456612260001323015470 0ustar matsmats/* Copyright 2013 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); 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.8.2/src/texttoolbutton.h0000644000175000017500000000173312260001323015404 0ustar matsmats/* Copyright 2013 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.8.2/src/contextwidget.cpp0000644000175000017500000000403612260001323015510 0ustar matsmats/* Copyright 2013 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_object = obj; connect(m_object, SIGNAL(changed()), this, SLOT(update()), Qt::UniqueConnection); ObjectWidget* ow = new ObjectWidget(m_object, this); 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; } //------------------------------------------------------------------------------ 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.8.2/src/collectionwidget.cpp0000644000175000017500000000636412260001323016165 0ustar matsmats/* Copyright 2013 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); m_itemLayout->addWidget(m_loadOlderButton); } //------------------------------------------------------------------------------ void CollectionWidget::onLoadOlderClicked() { updateLoadOlderButton(true); fetchOlder(); } //------------------------------------------------------------------------------ void CollectionWidget::updateLoadOlderButton(bool wait) { if (!m_list->size() || m_list->nextLink().isEmpty()) { m_loadOlderButton->setVisible(false); return; } QString text = tr("Load older"); if (wait) text = "..."; m_loadOlderButton->setText(text); m_loadOlderButton->setVisible(true); } //------------------------------------------------------------------------------ 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, this); connect(aw, SIGNAL(showContext(QASObject*)), this, SIGNAL(showContext(QASObject*))); return aw; } //------------------------------------------------------------------------------ bool CollectionWidget::countAsNew(QASAbstractObject* aObj) { QASActivity* act = qobject_cast(aObj); if (!act) return false; return !act->actor()->isYou(); } pumpa-0.8.2/src/fullobjectwidget.cpp0000644000175000017500000005403512260001323016161 0ustar matsmats/* Copyright 2013 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 #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_followAuthorButton(NULL), m_deleteButton(NULL), m_object(NULL), m_actor(NULL), m_author(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 QLabel(this); 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_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_commentButton = new TextToolButton(tr("comment"), 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(); 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); QHBoxLayout* acrossLayout = new QHBoxLayout; acrossLayout->setSpacing(10); acrossLayout->addWidget(m_actorWidget, 0, Qt::AlignTop); acrossLayout->addLayout(rightLayout); 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()), this, SLOT(updateFollowAuthorButton())); 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 (objType == "comment") { setLineWidth(1); setFrameStyle(QFrame::StyledPanel | QFrame::Plain); } else { setLineWidth(0); } if (!m_object->displayName().isEmpty()) { m_titleLabel->setText("" + m_object->displayName() + ""); m_titleLabel->setVisible(true); } else { m_titleLabel->setVisible(false); } if (objType == "image") { m_imageLabel->setVisible(true); m_imageUrl = m_object->imageUrl(); updateImage(); } else { m_imageLabel->setVisible(false); } m_author = m_object->author(); if (m_author) connect(m_author, SIGNAL(changed()), this, SLOT(updateFollowAuthorButton())); // m_commentable = objType == "note" || objType == "comment" || // objType == "image" || objType == "video"; m_commentable = objType != "person"; if (m_commentable) { m_favourButton->setVisible(true); m_followAuthorButton->setVisible(true); m_shareButton->setVisible(true); m_deleteButton->setVisible(m_author && m_author->isYou()); m_commentButton->setVisible(true); } else { m_favourButton->setVisible(false); m_followAuthorButton->setVisible(false); m_shareButton->setVisible(false); m_deleteButton->setVisible(false); m_commentButton->setVisible(false); } m_followButton->setVisible(objType == "person"); m_actor = m_object->asActor(); QASActor* actorOrAuthor = m_actor ? m_actor : m_author; m_actorWidget->setActor(actorOrAuthor); onChanged(); } //------------------------------------------------------------------------------ bool FullObjectWidget::hasValidIrtObject() { QASObject* irtObj = m_object->inReplyTo(); return irtObj && !irtObj->id().isEmpty(); } //------------------------------------------------------------------------------ void FullObjectWidget::onChanged() { if (!m_object) return; updateLikes(); updateShares(); updateFavourButton(); updateShareButton(); m_commentButton->setVisible(m_commentable && (m_object->type() != "comment" || hasValidIrtObject())); updateFollowButton(); updateFollowAuthorButton(); QString text = m_object->content(); if (m_actor) { text = m_actor->summary(); if (text.isEmpty()) text = tr("[No description]"); } setText(processText(text, true)); updateInfoText(); QASObjectList* ol = m_object->replies(); if (ol) { connect(ol, SIGNAL(changed()), this, SLOT(onChanged()), Qt::UniqueConnection); if (ol->size() > 0) 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")).arg(location); } else { infoStr = QString("%1"). arg(relativeFuzzyTime(m_object->published())). arg(m_object->url()); QASActor* author = m_object->author(); if (author) infoStr = QString("%1"). arg(author->displayName()). arg(author->url()) + " " + QString(tr("at %1")).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_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_followAuthorButton->setVisible(false); return; } m_followAuthorButton->setVisible(true); QString text = (m_author->followed() ? tr("stop following") : tr("follow")) + " "; text += m_author->preferredUsername(); m_followAuthorButton->setText(text); } //------------------------------------------------------------------------------ void FullObjectWidget::updateImage() { FileDownloader* fd = FileDownloader::get(m_imageUrl, true); connect(fd, SIGNAL(fileReady()), this, SLOT(updateImage()), Qt::UniqueConnection); m_imageLabel->setPixmap(fd->pixmap(":/images/broken_image.png")); } //------------------------------------------------------------------------------ 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); text += QString(" ") + tr("shared this."); } 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. Comments' lists returned by the pump API are with newest at the top, so we start from the end, and can assume that the next one is always newer. */ int i = 0; // index into m_repliesList for (size_t j=0; jsize(); j++) { QASObject* replyObj = ol->at(ol->size()-j-1); QString replyId = replyObj->id(); qint64 sortInt = replyObj->sortInt(); while (i < m_repliesList.size() && m_repliesList[i]->id() != replyId && m_repliesList[i]->sortInt() < sortInt) i++; if (m_repliesMap.contains(replyId)) continue; if (i < m_repliesList.size() && m_repliesList[i]->id() == replyId) 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); } if (ol->hasMore() && (qulonglong)m_repliesList.size() < ol->totalItems()) { addHasMoreButton(ol, li_before); } else if (m_hasMoreButton != NULL) { m_commentsLayout->removeWidget(m_hasMoreButton); delete m_hasMoreButton; m_hasMoreButton = NULL; } } //------------------------------------------------------------------------------ void FullObjectWidget::addHasMoreButton(QASObjectList* ol, int li) { QString buttonText = QString(tr("Show all %1 replies")). arg(ol->totalItems()); 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() { emit newReply(m_object, NULL, NULL); } //------------------------------------------------------------------------------ QString FullObjectWidget::typeName() const { QString typeName = tr("post"); QString tn = m_object->type(); if (tn == "note") typeName = tr("note"); else if (tn == "comment") typeName = tr("comment"); else if (tn == "image") typeName = tr("image"); 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::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); if (isFollowable(m_actor)) emit follow(m_actor->id(), !m_actor->followed()); } //------------------------------------------------------------------------------ void FullObjectWidget::onFollowAuthor() { bool doFollow = !m_author->followed(); if (!doFollow) { QString msg = tr("Are you sure you want to stop following") + " " + m_author->displayNameOrWebFinger(); int ret = QMessageBox::warning(this, CLIENT_FANCY_NAME, msg, QMessageBox::Cancel | QMessageBox::Yes, QMessageBox::Cancel); if (ret != QMessageBox::Yes) return; } updateFollowAuthorButton(true); if (isFollowable(m_author)) emit follow(m_author->id(), doFollow); } //------------------------------------------------------------------------------ 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_allowedTags << "a"; s_allowedTags << "img"; } 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; FileDownloader* fd = FileDownloader::get(imgSrc, true); connect(fd, SIGNAL(fileReady()), this, SLOT(onChanged()), Qt::UniqueConnection); if (fd->ready()) imagePlaceholder = QString(""). arg(fd->fileName()).arg(imgSrc); } } 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(); } } } pumpa-0.8.2/src/pumpasettings.h0000644000175000017500000001025512260001323015170 0ustar matsmats/* Copyright 2013 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 "pumpa_defines.h" class PumpaSettings : public QObject { Q_OBJECT public: PumpaSettings(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(); } 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", 80).toInt(); } int maxFirehoseItems() const { return getValue("max_timeline_items", 40).toInt(); } // 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); } 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; }; #endif /* _PUMPASETTINGS_H_ */ pumpa-0.8.2/src/objectlistwidget.cpp0000644000175000017500000000406612260001323016171 0ustar matsmats/* Copyright 2013 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(); } //------------------------------------------------------------------------------ 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.8.2/src/qasactor.h0000644000175000017500000000352712260001323014106 0ustar matsmats/* Copyright 2013 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" //------------------------------------------------------------------------------ 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(); } bool isYou() const { return m_isYou; } void setYou() { m_isYou = true; } bool followed() const { return m_followed; } bool followedJson() const { return m_followed_json; } void setFollowed(bool b); QString summary() const { return m_summary; } QString location() const { return m_location; } private: bool m_followed; bool m_followed_json; bool m_isYou; QString m_summary; QString m_location; QString m_webFinger; QString m_preferredUsername; static QMap s_actors; }; #endif /* _QASACTOR_H_ */ pumpa-0.8.2/src/messageedit.cpp0000644000175000017500000001426212260001323015114 0ustar matsmats/* Copyright 2013 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.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.8.2/src/objectlistwidget.h0000644000175000017500000000235112260001323015631 0ustar matsmats/* Copyright 2013 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.8.2/src/filedownloader.cpp0000644000175000017500000001670212260001323015621 0ustar matsmats/* Copyright 2013 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" #ifdef QT5 #include #else #include #endif #include //------------------------------------------------------------------------------ QString FileDownloader::m_cacheDir; QMap FileDownloader::m_downloading; QString FileDownloader::s_siteUrl; QString FileDownloader::s_clientId; QString FileDownloader::s_clientSecret; QString FileDownloader::s_token; QString FileDownloader::s_tokenSecret; //------------------------------------------------------------------------------ QString slashify(const QString& url) { QString ret = url; if (!ret.endsWith('/')) ret.append('/'); return ret; } //------------------------------------------------------------------------------ void FileDownloader::setOAuthInfo(QString siteUrl, QString clientId, QString clientSecret, QString token, QString tokenSecret) { s_siteUrl = siteUrl; s_clientId = clientId; s_clientSecret = clientSecret; s_token = token; s_tokenSecret = tokenSecret; } //------------------------------------------------------------------------------ FileDownloader::FileDownloader(const QString& url) : m_downloadingUrl(url), m_downloadStarted(false) { QString fn = urlToPath(m_downloadingUrl); if (QFile::exists(fn)) { m_cachedFile = fn; } else { m_cachedFile = ""; m_downloading.insert(m_downloadingUrl, this); oaRequest = new KQOAuthRequest(this); oaManager = new KQOAuthManager(this); connect(oaManager, SIGNAL(authorizedRequestReady(QByteArray, int)), this, SLOT(onAuthorizedRequestReady(QByteArray, int))); connect(oaManager, SIGNAL(errorMessage(QString)), this, SIGNAL(networkError(QString))); m_nam = new QNetworkAccessManager(this); connect(m_nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); connect(m_nam, SIGNAL(sslErrors(QNetworkReply*, const QList&)), this, SLOT(onSslErrors(QNetworkReply*, const QList&))); } } //------------------------------------------------------------------------------ FileDownloader* FileDownloader::get(const QString& url, bool download) { if (m_downloading.contains(url)) return m_downloading[url]; FileDownloader* fd = new FileDownloader(url); if (download && !fd->ready()) fd->download(); return fd; } //------------------------------------------------------------------------------ void FileDownloader::download() { if (m_downloadStarted) return; if (m_downloadingUrl.startsWith(s_siteUrl)) { oaRequest->initRequest(KQOAuthRequest::AuthorizedRequest, QUrl(m_downloadingUrl)); oaRequest->setConsumerKey(s_clientId); oaRequest->setConsumerSecretKey(s_clientSecret); oaRequest->setToken(s_token); oaRequest->setTokenSecret(s_tokenSecret); oaRequest->setHttpMethod(KQOAuthRequest::GET); oaManager->executeAuthorizedRequest(oaRequest, 0); } else { m_nam->get(QNetworkRequest(QUrl(m_downloadingUrl))); } m_downloadStarted = true; } //------------------------------------------------------------------------------ QString FileDownloader::fileName() const { return ready() ? m_cachedFile : urlToPath(m_downloadingUrl); } //------------------------------------------------------------------------------ QString FileDownloader::fileName(QString defaultImage) const { return ready() ? m_cachedFile : defaultImage; } //------------------------------------------------------------------------------ QPixmap FileDownloader::pixmap(QString defaultImage) const { QString fn = fileName(defaultImage); QPixmap pix(fn); if (pix.isNull()) pix.load(fn,"JPEG"); if (pix.isNull()) pix.load(fn,"PNG"); if (pix.isNull()) pix.load(defaultImage); return pix; } //------------------------------------------------------------------------------ void FileDownloader::onSslErrors(QNetworkReply* reply, const QList&) { reply->ignoreSslErrors(); } //------------------------------------------------------------------------------ void FileDownloader::replyFinished(QNetworkReply* nr) { if (nr->error()) { emit networkError(tr("Network error: ")+nr->errorString()); return; } onAuthorizedRequestReady(nr->readAll(), 0); nr->deleteLater(); } //------------------------------------------------------------------------------ void FileDownloader::onAuthorizedRequestReady(QByteArray response, int) { m_downloading.remove(m_downloadingUrl); if (oaManager->lastError()) { emit networkError(QString(tr("Unable to download %1 (Error #%2).")) .arg(m_downloadingUrl) .arg(oaManager->lastError())); return; } QString fn = urlToPath(m_downloadingUrl); 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()); return; } fp->write(response); fp->close(); QPixmap pix = pixmap(fn); resizeImage(pix, fn); emit fileReady(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); } //------------------------------------------------------------------------------ QString FileDownloader::urlToPath(const QString& 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. */ #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.8.2/src/messagewindow.h0000644000175000017500000000622712260001323015145 0ustar matsmats/* Copyright 2013 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 "messageedit.h" #include "qactivitystreams.h" #include "pumpasettings.h" #include "texttoolbutton.h" #include "richtextlabel.h" #include "messagerecipients.h" //------------------------------------------------------------------------------ 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 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); private slots: void onAddPicture(); void onRemovePicture(); void togglePreview(); void updatePreview(); void onAddRecipient(QASActor*); void onAddTo(); void onAddCc(); void onMarkdownChecked(int); private: void addRecipientWindow(MessageRecipients*, QString); void updateAddPicture(); // void initRecipients(MessageRecipients*, QASObjectList*, int); 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; RichTextLabel* m_previewLabel; QLabel* m_pictureLabel; QLineEdit* m_title; QPushButton* m_cancelButton; QPushButton* m_sendButton; QPushButton* m_previewButton; QString m_imageFileName; MessageRecipients* m_toRecipients; MessageRecipients* m_ccRecipients; RecipientList m_parentTo; RecipientList m_parentCc; QMap m_recipientSelection; QStringList m_recipientList; QASObject* m_obj; PumpaSettings* m_s; const RecipientList* m_rl; }; #endif pumpa-0.8.2/src/imagelabel.h0000644000175000017500000000210712260001323014344 0ustar matsmats/* Copyright 2013 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.8.2/src/QtKOAuth0000644000175000017500000000022412260001323013472 0ustar matsmats#include "kqoauthrequest.h" #include "kqoauthrequest_1.h" #include "kqoauthrequest_xauth.h" #include "kqoauthmanager.h" #include "kqoauthglobals.h" pumpa-0.8.2/src/aswidget.cpp0000644000175000017500000001605512260001323014433 0ustar matsmats/* Copyright 2013 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::fetchNewer() { emit request(m_list->prevLink(), m_asMode | QAS_NEWER); } //------------------------------------------------------------------------------ void ASWidget::fetchOlder() { m_purgeCounter = m_purgeWait; QString nextLink = m_list->nextLink(); if (!nextLink.isEmpty()) 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; QASAbstractObject* wObj = objectAt(li); if (wObj == cObj) { li++; older = true; continue; } if (m_object_set.contains(cObj)) { // qDebug() << "[WARNING]" << cObj->apiLink() << "in wrong order in list" // << m_list->url(); 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); ow->changeObject(cObj); m_itemLayout->insertWidget(li++, ow); doCountAsNew = countAsNew(cObj); } else { ObjectWidgetWithSignals* ow = createWidget(cObj); doCountAsNew = countAsNew(cObj); ObjectWidgetWithSignals::connectSignals(ow, this); 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 && !isVisible() && !m_firstTime) emit highlightMe(); m_firstTime = false; } //------------------------------------------------------------------------------ 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) { emit request(obj->apiLink(), obj->asType()); obj->lastRefreshed(now); } } pumpa-0.8.2/src/kQOAuth/0000755000175000017500000000000012260001323013425 5ustar matsmatspumpa-0.8.2/src/kQOAuth/kqoauthmanager.h0000644000175000017500000002145112260001323016610 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 "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); 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.8.2/src/kQOAuth/kqoauthmanager_p.h0000644000175000017500000000515012260001323017125 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.8.2/src/kQOAuth/kqoauthrequest_p.h0000644000175000017500000000552512260001323017211 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.8.2/src/kQOAuth/kqoauthmanager.cpp0000644000175000017500000006103512260001323017145 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); 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) { 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(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) { 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; icontentType() == "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.8.2/src/kQOAuth/kqoauthrequest_xauth.h0000644000175000017500000000305312260001323020075 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.8.2/src/kQOAuth/kqoauthrequest_1.h0000644000175000017500000000200712260001323017102 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.8.2/src/kQOAuth/kqoauthutils.cpp0000644000175000017500000000477712260001323016705 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.8.2/src/kQOAuth/kqoauthrequest_xauth.cpp0000644000175000017500000000521612260001323020433 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.8.2/src/kQOAuth/kqoauthrequest_1.cpp0000644000175000017500000000157212260001323017443 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.8.2/src/kQOAuth/kqoauthglobals.h0000644000175000017500000000367612260001323016632 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.8.2/src/kQOAuth/kqoauthutils.h0000644000175000017500000000204012260001323016327 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.8.2/src/kQOAuth/kqoauthauthreplyserver.h0000644000175000017500000000260112260001323020436 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.8.2/src/kQOAuth/kqoauthrequest.h0000644000175000017500000001133412260001323016665 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 }; /** * 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.8.2/src/kQOAuth/Makefile0000644000175000017500000000003012260001323015056 0ustar matsmatsall: $(MAKE) -C ../../ pumpa-0.8.2/src/kQOAuth/kqoauthrequest.cpp0000644000175000017500000004325312260001323017225 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; 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.8.2/src/kQOAuth/kqoauthrequest_xauth_p.h0000644000175000017500000000212012260001323020406 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.8.2/src/kQOAuth/kqoauthauthreplyserver.cpp0000644000175000017500000000671412260001323021002 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.8.2/src/kQOAuth/kqoauthauthreplyserver_p.h0000644000175000017500000000302312260001323020754 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.8.2/src/recipientedit.h0000644000175000017500000000242712260001323015117 0ustar matsmats/* Copyright 2013 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.8.2/src/pumpasettings.cpp0000644000175000017500000000563112260001323015525 0ustar matsmats/* Copyright 2013 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(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); } //------------------------------------------------------------------------------ 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.8.2/src/messagerecipients.h0000644000175000017500000000263612260001323016003 0ustar matsmats/* Copyright 2013 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.8.2/src/activitywidget.h0000644000175000017500000000364112260001323015326 0ustar matsmats/* Copyright 2013 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, QWidget* parent=0); virtual ~ActivityWidget(); virtual void changeObject(QASAbstractObject* obj); 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(); virtual void onReply(QASObject*, QASObjectList*, QASObjectList*); signals: void showContext(QASObject*); private: void updateText(); QString recipientsToString(QASObjectList* rec); // ObjectWidget* makeObjectWidgetAndConnect(QASObject* obj, bool shortObject); RichTextLabel* m_textLabel; ActorWidget* m_actorWidget; ObjectWidget* m_objectWidget; QASActivity* m_activity; }; #endif /* _ACTIVITYWIDGET_H_ */ pumpa-0.8.2/src/fullobjectwidget.h0000644000175000017500000000621112260001323015617 0ustar matsmats/* Copyright 2013 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 "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(); 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 onDeleteClicked(); private: QString typeName() const; QString textExcerpt() const; bool hasValidIrtObject(); void setText(QString text); void updateInfoText(); void updateLikes(); void updateShares(); 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_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; QVBoxLayout* m_contentLayout; QHBoxLayout* m_buttonLayout; QVBoxLayout* m_commentsLayout; QASObject* m_object; QASActor* m_actor; QASActor* m_author; QList m_repliesList; QSet m_repliesMap; bool m_childWidget; bool m_commentable; }; #endif /* _FULLOBJECTWIDGET_H_ */ pumpa-0.8.2/src/pumpapp.cpp0000644000175000017500000013240612260001323014305 0ustar matsmats/* Copyright 2013 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 "pumpapp.h" #include "json.h" #include "util.h" #include "filedownloader.h" #include "qaspell.h" //------------------------------------------------------------------------------ PumpApp::PumpApp(PumpaSettings* settings, QString locale, QWidget* parent) : QMainWindow(parent), m_nextRequestId(0), m_s(settings), m_contextWidget(NULL), m_isLoading(false), m_wiz(NULL), m_messageWindow(NULL), m_trayIcon(NULL), m_locale(locale), m_uploadDialog(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()); // 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); oaManager = new KQOAuthManager(this); connect(oaManager, SIGNAL(authorizedRequestReady(QByteArray, int)), this, SLOT(onAuthorizedRequestReady(QByteArray, int))); 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_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_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"); 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()); } //------------------------------------------------------------------------------ void PumpApp::launchOAuthWizard() { if (!m_wiz) { m_wiz = new OAuthWizard(m_nam, 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(rejected()), this, SLOT(exit())); connect(m_wiz, SIGNAL(accepted()), this, SLOT(show())); } m_wiz->restart(); m_wiz->show(); } //------------------------------------------------------------------------------ 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(); QVariantMap publicJson; publicJson["displayName"] = tr("Public"); publicJson["objectType"] = "collection"; publicJson["id"] = PUBLIC_RECIPIENT_ID; m_recipientLists.append(QASObject::getObject(publicJson, this)); QVariantMap followersJson; followersJson["displayName"] = tr("Followers"); followersJson["objectType"] = "collection"; followersJson["id"] = apiUrl(apiUser("followers")); m_recipientLists.append(QASObject::getObject(followersJson, this)); request(apiUser(""), 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*))); } //------------------------------------------------------------------------------ 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(); } //------------------------------------------------------------------------------ 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(); } //------------------------------------------------------------------------------ void PumpApp::refreshTimeLabels() { m_inboxWidget->refreshTimeLabels(); m_directMinorWidget->refreshTimeLabels(); m_directMajorWidget->refreshTimeLabels(); m_inboxMinorWidget->refreshTimeLabels(); m_firehoseWidget->refreshTimeLabels(); if (m_contextWidget) m_contextWidget->refreshTimeLabels(); } //------------------------------------------------------------------------------ void PumpApp::syncOAuthInfo() { FileDownloader::setOAuthInfo(m_s->siteUrl(), m_s->clientId(), m_s->clientSecret(), m_s->token(), m_s->tokenSecret()); } //------------------------------------------------------------------------------ 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) { QString msg = QString(tr("You have %1 new notifications.")).arg(acts.count()); // 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 (acts.count() == 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::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()); } //------------------------------------------------------------------------------ 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())); 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())); 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_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(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); menuBar()->addMenu(m_tabsMenu); helpMenu = new QMenu(tr("&Help"), this); helpMenu->addAction(aboutAction); helpMenu->addAction(aboutQtAction); menuBar()->addMenu(helpMenu); } //------------------------------------------------------------------------------ void PumpApp::preferences() { m_settingsDialog->exec(); } //------------------------------------------------------------------------------ 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 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::newNote(QASObject* obj, QASObjectList* to, QASObjectList* cc) { 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))); m_messageWindow->setCompletions(&m_completions); } m_messageWindow->newMessage(obj, to, cc); m_messageWindow->show(); } //------------------------------------------------------------------------------ void PumpApp::reload() { fetchAll(true); refreshTimeLabels(); } //------------------------------------------------------------------------------ void PumpApp::fetchAll(bool all) { m_inboxWidget->fetchNewer(); m_directMinorWidget->fetchNewer(); m_directMajorWidget->fetchNewer(); m_inboxMinorWidget->fetchNewer(); if (tabShown(m_firehoseWidget)) m_firehoseWidget->fetchNewer(); 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(); } //------------------------------------------------------------------------------ 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::testUserAndFollow(QString username, QString server) { QString fingerUrl = QString("%1/.well-known/webfinger?resource=%2@%1"). arg(server).arg(username); // https://io.saz.im/.well-known/webfinger?resource=sazius@saz.im 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) { if (!m_contextWidget) { m_contextWidget = new ContextWidget(this); connectCollection(m_contextWidget); } if (!tabShown(m_contextWidget)) m_tabWidget->addTab(m_contextWidget, tr("&Context"), true, true); m_contextWidget->setObject(obj); m_tabWidget->setCurrentWidget(m_contextWidget); } //------------------------------------------------------------------------------ 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()); if (!title.isEmpty()) obj["displayName"] = title; feed("post", obj, QAS_OBJECT | QAS_REFRESH | QAS_POST, to, cc); } //------------------------------------------------------------------------------ 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"] = title; m_imageTo = to; m_imageCc = cc; uploadFile(imageFile); } //------------------------------------------------------------------------------ void PumpApp::uploadFile(QString filename) { 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); } m_uploadDialog->setValue(0); m_uploadDialog->show(); const QNetworkReply* nr = executeRequest(oaRequest, QAS_IMAGE_UPLOAD); connect(nr, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64))); } //------------------------------------------------------------------------------ void PumpApp::updatePostedImage(QVariantMap obj) { 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, to); } //------------------------------------------------------------------------------ void PumpApp::postImageActivity(QVariantMap) { feed("post", m_imageObject, QAS_ACTIVITY | QAS_REFRESH | QAS_POST, m_imageTo, m_imageCc); } //------------------------------------------------------------------------------ void PumpApp::uploadProgress(qint64 bytesSent, qint64 bytesTotal) { if (!m_uploadDialog || bytesTotal <= 0) return; QNetworkReply *reply = qobject_cast(sender()); m_uploadDialog->setValue((100*bytesSent)/bytesTotal); if (m_uploadDialog->wasCanceled() && reply) { reply->abort(); m_uploadDialog->hide(); } } //------------------------------------------------------------------------------ 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::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]" : "[POST]") << 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) { 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); setLoading(true); notifyMessage(tr("Loading ...")); } //------------------------------------------------------------------------------ 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)); oaManager->executeAuthorizedRequest(request, id); return oaManager->getReply(request); } //------------------------------------------------------------------------------ void PumpApp::followActor(QASActor* actor, bool doFollow) { actor->setFollowed(doFollow); QString from = QString("%2 (%1)").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 = oaManager->lastError(); QPair rp = m_requestMap.take(rid); KQOAuthRequest* request = rp.first; int id = rp.second; QString reqUrl = request->requestEndpoint().toString(); #ifdef DEBUG_NET qDebug() << "[DEBUG] request done [" << rid << id << "]" << reqUrl << response.count() << "bytes"; #endif #ifdef DEBUG_NET_MOAR qDebug() << 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_OBJECT) { qDebug() << "[WARNING] unable to fetch context for object."; } else { errorMessage(QString(tr("Network or authorisation error [%1/%2] %3.")). arg(oaManager->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* coll = QASCollection::getCollection(json, this, id); if (coll) { bool checkFollows = (id & QAS_FOLLOW); for (size_t i=0; isize(); ++i) { QASActivity* activity = coll->at(i); QASObject* obj = activity->object(); if (obj) { QASObject* irtObj = obj->inReplyTo(); if (irtObj && irtObj->url().isEmpty()) refreshObject(irtObj); } if (checkFollows) { QASActor* actor = activity->actor(); if (activity->verb() == "post" && actor && actor->followedJson() && !actor->followed()) followActor(actor); } } } } else if (sid == QAS_ACTIVITY) { QASActivity* act = QASActivity::getActivity(json, this); QASObject* obj = act->object(); 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); } } } else if (sid == QAS_OBJECT) { QASObject::getObject(json, this); } else if (sid == QAS_ACTORLIST) { QASActorList::getActorList(json, this); } else if (sid == QAS_SELF_PROFILE) { m_selfActor = QASActor::getActor(json["profile"].toMap(), this); m_selfActor->setYou(); } 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->hide(); updatePostedImage(json); } else if (sid == QAS_IMAGE_UPDATE) { postImageActivity(json); } if ((id & QAS_POST) && m_messageWindow) 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.8.2/src/oauthwizard.cpp0000644000175000017500000002335112260001323015162 0ustar matsmats/* Copyright 2013 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 "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())); layout->addWidget(accountIdLabel); layout->addWidget(accountIdEdit); registerField("accountId*", accountIdEdit); 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, QWidget* parent) : QWizard(parent), m_nam(nam) { setWindowTitle(CLIENT_FANCY_NAME); m_oam = new KQOAuthManager(this); 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); 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) { if (m_clientRegTryCount == 1 && m_server.startsWith("https://")) { m_server.replace("https://", "http://"); registerOAuthClient(); } else 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.8.2/src/imagelabel.cpp0000644000175000017500000000222112260001323014674 0ustar matsmats/* Copyright 2013 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.8.2/src/messagerecipients.cpp0000644000175000017500000000557612260001323016344 0ustar matsmats/* Copyright 2013 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_list.size()-1; 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.8.2/src/qasactivity.h0000644000175000017500000000455112260001323014630 0ustar matsmats/* Copyright 2013 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; } 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"); } 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.8.2/src/recipientedit.cpp0000644000175000017500000000560012260001323015446 0ustar matsmats/* Copyright 2013 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.8.2/src/collectionwidget.h0000644000175000017500000000276212260001323015630 0ustar matsmats/* Copyright 2013 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 void fetchNewer(); 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 bool countAsNew(QASAbstractObject* aObj); virtual void clear(); QPushButton* m_loadOlderButton; }; #endif /* _COLLECTIONWIDGET_H_ */ pumpa-0.8.2/src/Makefile0000644000175000017500000000002412260001323013545 0ustar matsmatsall: $(MAKE) -C .. pumpa-0.8.2/src/qasactorlist.h0000644000175000017500000000264712260001323015004 0ustar matsmats/* Copyright 2013 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.8.2/src/pumpa_defines.h0000644000175000017500000000520412260001323015102 0ustar matsmats/* Copyright 2013 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.8.2" #define CLIENT_ICON ":/images/pumpa.png" #define WEBSITE_URL "http://saz.im/software/pumpa.html" #define MARKUP_DOC_URL "http://saz.im/software/pumpa.html#markdown" #define BUGTRACKER_URL "https://bugs.saz.im/pumpa" #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 // The higher bits can be used for info for the whatever method 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 MAX_WORD_LENGTH 40 #define MAX_SUGGESTIONS 10 //------------------------------------------------------------------------------ #endif /* _PUMPA_DEFINES_H_ */ pumpa-0.8.2/src/pumpasettingsdialog.h0000644000175000017500000000374112260001323016352 0ustar matsmats/* Copyright 2013 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; 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.8.2/src/objectwidget.h0000644000175000017500000000374712260001323014747 0ustar matsmats/* Copyright 2013 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(); signals: void moreClicked(); void showContext(QASObject*); private slots: void showMore(); 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; bool m_short; }; #endif /* _OBJECTWIDGET_H_ */ pumpa-0.8.2/src/objectwidgetwithsignals.h0000644000175000017500000000327412260001323017217 0ustar matsmats/* Copyright 2013 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, bool skipNewReply=false); 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 request(QString, int); protected: void refreshObject(QASAbstractObject* obj); }; #endif /* _OBJECTWIDGETWITHSIGNALS_H_ */ pumpa-0.8.2/src/tabwidget.cpp0000644000175000017500000000503512260001323014572 0ustar matsmats/* Copyright 2013 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) { sMap = new QSignalMapper(this); connect(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 { okToClose.insert(index); } return index; } //------------------------------------------------------------------------------ void TabWidget::closeTab(int index) { if (!okToClose.contains(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) { sMap->setMapping(page, index); connect(page, SIGNAL(highlightMe()), sMap, SLOT(map())); } pumpa-0.8.2/src/qasactor.cpp0000644000175000017500000000622612260001323014440 0ustar matsmats/* Copyright 2013 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; void QASActor::clearCache() { deleteMap(s_actors); } //------------------------------------------------------------------------------ QASActor::QASActor(QString id, QObject* parent) : QASObject(id, parent), m_followed(false), m_followed_json(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_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); 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; } //------------------------------------------------------------------------------ void QASActor::setFollowed(bool b) { if (b != m_followed) { m_followed = b; emit changed(); } } //------------------------------------------------------------------------------ QString QASActor::displayNameOrWebFinger() const { if (displayName().isEmpty()) return webFinger(); return displayName(); } pumpa-0.8.2/src/filedownloader.h0000644000175000017500000000445512260001323015270 0ustar matsmats/* Copyright 2013 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 "QtKOAuth" class FileDownloader : public QObject { Q_OBJECT public: static void setOAuthInfo(QString siteUrl, QString clientId, QString clientSecret, QString token, QString tokenSecret); static FileDownloader* get(const QString& url, bool download=false); void download(); bool downloading() const { return m_downloadStarted; } bool ready() const { return !m_cachedFile.isEmpty(); } QString fileName() const; QString fileName(QString defaultImage) const; QPixmap pixmap(QString defaultImage=":/images/broken_image.png") const; static QString getCacheDir() { return m_cacheDir; } static QString urlToPath(const QString& url); signals: void networkError(const QString&); void fileReady(const QString&); void fileReady(); private slots: void onAuthorizedRequestReady(QByteArray response, int id); void onSslErrors(QNetworkReply* reply, const QList&); void replyFinished(QNetworkReply* nr); private: FileDownloader(); FileDownloader(const QString&); static void resizeImage(QPixmap pix, QString fn); KQOAuthManager *oaManager; KQOAuthRequest *oaRequest; QNetworkAccessManager* m_nam; QString m_downloadingUrl; QString m_cachedFile; bool m_downloadStarted; static QString m_cacheDir; static QMap m_downloading; static QString s_siteUrl; static QString s_clientId; static QString s_clientSecret; static QString s_token; static QString s_tokenSecret; }; #endif pumpa-0.8.2/src/richtextlabel.cpp0000644000175000017500000000355012260001323015452 0ustar matsmats/* Copyright 2013 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); } //------------------------------------------------------------------------------ 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.8.2/src/contextwidget.h0000644000175000017500000000220512260001323015151 0ustar matsmats/* Copyright 2013 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); protected: virtual void update(); bool updateNumReplies(); int m_numReplies; QASObject* m_object; }; #endif /* _CONTEXTWIDGET_H_ */ pumpa-0.8.2/src/objectwidget.cpp0000644000175000017500000001222612260001323015272 0ustar matsmats/* Copyright 2013 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); m_layout->addWidget(m_objectWidget); m_shortObjectWidget = new ShortObjectWidget(m_object, this); 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); } emit moreClicked(); } //------------------------------------------------------------------------------ void ObjectWidget::onChanged() { setVisible(!m_object->url().isEmpty() && !m_object->isDeleted()); } //------------------------------------------------------------------------------ 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(); } pumpa-0.8.2/src/actorwidget.cpp0000644000175000017500000000420612260001323015133 0ustar matsmats/* Copyright 2013 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" //------------------------------------------------------------------------------ ActorWidget::ActorWidget(QASActor* a, QWidget* parent, bool small) : QLabel(parent), m_actor(a) { #ifdef DEBUG_WIDGETS qDebug() << "Creating ActorWidget" << (m_actor ? m_actor->id() : "NULL"); #endif int max_size = small ? 32 : 64; setScaledContents(true); setMaximumSize(max_size, max_size); setFocusPolicy(Qt::NoFocus); 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(); } //------------------------------------------------------------------------------ void ActorWidget::onImageChanged() { m_url = m_actor ? m_actor->imageUrl() : ""; updatePixmap(); } //------------------------------------------------------------------------------ void ActorWidget::updatePixmap() { if (m_url.isEmpty()) { setPixmap(QPixmap(":/images/default.png")); return; } FileDownloader* fd = FileDownloader::get(m_url, true); connect(fd, SIGNAL(fileReady()), this, SLOT(updatePixmap()), Qt::UniqueConnection); setPixmap(fd->pixmap(":/images/default.png")); } pumpa-0.8.2/src/actorwidget.h0000644000175000017500000000236212260001323014601 0ustar matsmats/* Copyright 2013 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 "qactivitystreams.h" //------------------------------------------------------------------------------ class ActorWidget : public QLabel { Q_OBJECT public: ActorWidget(QASActor* a, QWidget* parent = 0, bool small=false); virtual ~ActorWidget(); void setActor(QASActor* a); public slots: void onImageChanged(); void updatePixmap(); private: QASActor* m_actor; QString m_url; QString m_localFile; }; #endif /* _ACTORWIDGET_H_ */ pumpa-0.8.2/src/qasobject.cpp0000644000175000017500000002036112260001323014572 0ustar matsmats/* Copyright 2013 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()); //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... if (repliesMap["items"].toList().size()) { 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()) 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::excerpt() const { 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.8.2/src/qasabstractobjectlist.h0000644000175000017500000000432212260001323016656 0ustar matsmats/* Copyright 2013 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); 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); } 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.8.2/src/qaspell.cpp0000644000175000017500000000607012260001323014261 0ustar matsmats/* Copyright 2013 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.8.2/src/qascollection.h0000644000175000017500000000311712260001323015124 0ustar matsmats/* Copyright 2013 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.8.2/src/qasactivity.cpp0000644000175000017500000000661212260001323015163 0ustar matsmats/* Copyright 2013 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)); //connectSignals(m_object); if (!m_object->author()) m_object->setAuthor(m_actor); } updateVar(json, m_published, "published", ch); updateVar(json, m_updated, "updated", ch); updateVar(json, m_generatorName, "generator", "displayName", ch); if (m_verb == "post" && 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(); Q_ASSERT_X(!id.isEmpty(), "getActivity", serializeJsonC(json)); 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(); } pumpa-0.8.2/src/pumpasettingsdialog.cpp0000644000175000017500000001434012260001323016702 0ustar matsmats/* Copyright 2013 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); 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_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->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.8.2/src/objectwidgetwithsignals.cpp0000644000175000017500000000624412260001323017552 0ustar matsmats/* Copyright 2013 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, bool skipNewReply) { 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*))); if (!skipNewReply) 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(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(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.8.2/src/util.cpp0000644000175000017500000001610612260001323013576 0ustar matsmats/* Copyright 2013 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" //------------------------------------------------------------------------------ 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; } //------------------------------------------------------------------------------ QString siteUrlFixer(QString url) { if (!url.startsWith("http://") && !url.startsWith("https://")) url = "https://" + 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) { QString dateStr = sTime.toString("ddd d MMMM yyyy"); int secs = sTime.secsTo(QDateTime::currentDateTime().toUTC()); if (secs < 0) secs = 0; int mins = qRound((float)secs/60); int hours = qRound((float)secs/60/60); if (secs < 60) { dateStr = QObject::tr("a few seconds ago"); } else if (mins == 1) { dateStr = QObject::tr("one minute ago"); } else if (mins < 60) { dateStr = QObject::tr("%n minute(s) ago", 0, mins); } else if (hours >= 1 && hours < 24) { dateStr = QObject::tr("%n hour(s) ago", 0, hours); } 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 addTextMarkup(QString text, bool useMarkdown) { QString oldText = text; #ifdef DEBUG_MARKUP qDebug() << "\n[DEBUG] MARKUP\n" << text; #endif // 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(); } } #ifdef DEBUG_MARKUP qDebug() << "\n[DEBUG] MARKUP (clean inline HTML)\n" << text; #endif // linkify plain URLs text = linkifyUrls(text, useMarkdown); #ifdef DEBUG_MARKUP qDebug() << "\n[DEBUG] MARKUP (linkify plain URLs)\n" << text; #endif if (useMarkdown) { // apply markdown text = markDown(text); } else { text.replace("\n", "
    "); } #ifdef DEBUG_MARKUP qDebug() << "\n[DEBUG] MARKUP (apply " << (useMarkdown?"Markdown":"text conversion") << ")\n" << text; #endif return text; } //------------------------------------------------------------------------------ pumpa-0.8.2/src/messagewindow.cpp0000644000175000017500000003267712260001323015510 0ustar matsmats/* Copyright 2013 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; //------------------------------------------------------------------------------ MessageWindow::MessageWindow(PumpaSettings* s, const RecipientList* rl, QWidget* parent) : QDialog(parent), m_addressLayout(NULL), 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:")); m_ccLabel = new QLabel(tr("Cc:")); 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 QLabel(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); m_title->setPlaceholderText(tr("Title (optional)")); m_previewLabel = new RichTextLabel(this); m_previewLabel->setLineWidth(1); m_previewLabel->setFrameStyle(QFrame::Box); m_previewLabel->hide(); 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_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_textEdit); m_layout->addWidget(m_previewLabel); m_cancelButton = new QPushButton(tr("Cancel")); 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 (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); if (m_obj != NULL) { // if this is a reply the to list is hidden by default, so we need // to make it visible m_toRecipients->setVisible(true); m_addressLayout->labelForField(m_toRecipients)->setVisible(true); } } //------------------------------------------------------------------------------ void MessageWindow::newMessage(QASObject* obj, QASObjectList* to, QASObjectList* cc) { QASObject* origObj = obj; if (obj && !m_s->commentOnComments() && obj->inReplyTo()) obj = obj->inReplyTo(); bool isReply = (obj != NULL); m_obj = obj; QString title = isReply ? tr("Post a reply") : tr("Post a note"); setWindowTitle(QString(CLIENT_FANCY_NAME) + " - " + title); m_markdownCheckBox->setChecked(m_s->useMarkdown()); m_recipientList.clear(); for (int i=0; isize(); ++i) { QASObject* obj = m_rl->at(i); addToRecipientList(obj->displayName() + " (list)", obj); } 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()); m_infoLabel->setText(title); m_parentTo.clear(); m_parentCc.clear(); bool hasInitialTo = false; if (!isReply) { m_toLabel->setText(tr("To:")); // A new post, use default recipients setDefaultRecipients(m_toRecipients, m_s->defaultToAddress()); setDefaultRecipients(m_ccRecipients, m_s->defaultCcAddress()); } else { m_toLabel->setText(tr("Mentions:")); // a reply, we need to keep track of To/Cc of parent m_toRecipients->clear(); m_ccRecipients->clear(); // copy to/cc from post we are replying to if (to) m_parentTo = to->toRecipientList(); if (cc) m_parentCc = cc->toRecipientList(); // add original post author QASObject* origAuthor = obj->author(); if (origAuthor && !m_parentTo.contains(origAuthor)) { m_parentTo.append(origAuthor); } // if this is a reply to a comment add comment author to editable // To list if (origObj != obj && origObj->author()) { m_toRecipients->addRecipient(origObj->author()); hasInitialTo = true; } } m_toRecipients->setVisible(!isReply || hasInitialTo); m_addressLayout->labelForField(m_toRecipients)-> setVisible(!isReply || hasInitialTo); m_ccRecipients->setVisible(!isReply); m_addressLayout->labelForField(m_ccRecipients)->setVisible(!isReply); m_addToButton->setVisible(!isReply); m_addCcButton->setVisible(!isReply); updateAddPicture(); } //------------------------------------------------------------------------------ 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->clear(); } //------------------------------------------------------------------------------ void MessageWindow::showEvent(QShowEvent*) { m_textEdit->setFocus(Qt::OtherFocusReason); m_textEdit->selectAll(); activateWindow(); } //------------------------------------------------------------------------------ void MessageWindow::accept() { m_textEdit->hideCompletion(); QString msg = m_textEdit->toPlainText(); if (m_obj == NULL) { RecipientList to = m_toRecipients->recipients(); RecipientList cc = m_ccRecipients->recipients(); QString title = m_title->text(); if (m_imageFileName.isEmpty()) { emit sendMessage(msg, title, to, cc); } else { emit sendImage(msg, title, m_imageFileName, to, cc); } } else { RecipientList to = m_parentTo; RecipientList cc = m_parentCc; RecipientList newTo = m_toRecipients->recipients(); for (int i=0; isetVisible(false); m_removePictureButton->setVisible(false); m_pictureLabel->setVisible(false); m_title->setVisible(false); 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 { m_pictureLabel->setPixmap(p); m_addPictureButton->setText(tr("&Change picture")); m_removePictureButton->setVisible(true); m_pictureLabel->setVisible(true); } } //------------------------------------------------------------------------------ void MessageWindow::updatePreview() { if (m_previewLabel->isVisible()) m_previewLabel->setText(addTextMarkup(m_textEdit->toPlainText(), m_s->useMarkdown())); } //------------------------------------------------------------------------------ void MessageWindow::togglePreview() { m_previewLabel->setVisible(!m_previewLabel->isVisible()); updatePreview(); }